Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- CHANGELOG.md +35 -0
- __init__.py +96 -34
- __pycache__/__init__.cpython-310.pyc +0 -0
- __pycache__/cli.cpython-310.pyc +0 -0
- __pycache__/commit_scheduler.cpython-310.pyc +0 -0
- __pycache__/context_vars.cpython-310.pyc +0 -0
- __pycache__/deploy.cpython-310.pyc +0 -0
- __pycache__/dummy_commit_scheduler.cpython-310.pyc +0 -0
- __pycache__/histogram.cpython-310.pyc +0 -0
- __pycache__/imports.cpython-310.pyc +0 -0
- __pycache__/run.cpython-310.pyc +0 -0
- __pycache__/sqlite_storage.cpython-310.pyc +0 -0
- __pycache__/table.cpython-310.pyc +0 -0
- __pycache__/typehints.cpython-310.pyc +0 -0
- __pycache__/utils.cpython-310.pyc +0 -0
- cli.py +58 -2
- deploy.py +55 -7
- imports.py +4 -2
- media/__pycache__/__init__.cpython-310.pyc +0 -0
- media/__pycache__/audio_writer.cpython-310.pyc +0 -0
- media/__pycache__/file_storage.cpython-310.pyc +0 -0
- media/__pycache__/media.cpython-310.pyc +0 -0
- media/__pycache__/utils.cpython-310.pyc +0 -0
- media/__pycache__/video_writer.cpython-310.pyc +0 -0
- package.json +1 -1
- run.py +62 -34
- table.py +117 -7
- ui/__pycache__/__init__.cpython-310.pyc +0 -0
- ui/__pycache__/fns.cpython-310.pyc +0 -0
- ui/__pycache__/main.cpython-310.pyc +0 -0
- ui/__pycache__/run_detail.cpython-310.pyc +0 -0
- ui/__pycache__/runs.cpython-310.pyc +0 -0
- ui/fns.py +3 -0
- ui/helpers/__pycache__/run_selection.cpython-310.pyc +0 -0
- ui/main.py +160 -81
- ui/run_detail.py +3 -3
- ui/runs.py +9 -9
- utils.py +44 -12
CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
| 1 |
# trackio
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
## 0.7.0
|
| 4 |
|
| 5 |
### Features
|
|
|
|
| 1 |
# trackio
|
| 2 |
|
| 3 |
+
## 0.10.0
|
| 4 |
+
|
| 5 |
+
### Features
|
| 6 |
+
|
| 7 |
+
- [#305](https://github.com/gradio-app/trackio/pull/305) [`e64883a`](https://github.com/gradio-app/trackio/commit/e64883a51f7b8b93f7d48b8afe55acdb62238b71) - bump to gradio 6.0, make `trackio` compatible, and fix related issues. Thanks @abidlabs!
|
| 8 |
+
|
| 9 |
+
## 0.9.1
|
| 10 |
+
|
| 11 |
+
### Features
|
| 12 |
+
|
| 13 |
+
- [#344](https://github.com/gradio-app/trackio/pull/344) [`7e01024`](https://github.com/gradio-app/trackio/commit/7e010241d9a34794e0ce0dc19c1a6f0cf94ba856) - Avoid redundant calls to /whoami-v2. Thanks @Wauplin!
|
| 14 |
+
|
| 15 |
+
## 0.9.0
|
| 16 |
+
|
| 17 |
+
### Features
|
| 18 |
+
|
| 19 |
+
- [#343](https://github.com/gradio-app/trackio/pull/343) [`51bea30`](https://github.com/gradio-app/trackio/commit/51bea30f2877adff8e6497466d3a799400a0a049) - Sync offline projects to Hugging Face spaces. Thanks @candemircan!
|
| 20 |
+
- [#341](https://github.com/gradio-app/trackio/pull/341) [`4fd841f`](https://github.com/gradio-app/trackio/commit/4fd841fa190e15071b02f6fba7683ef4f393a654) - Adds a basic UI test to `trackio`. Thanks @abidlabs!
|
| 21 |
+
- [#339](https://github.com/gradio-app/trackio/pull/339) [`011d91b`](https://github.com/gradio-app/trackio/commit/011d91bb6ae266516fd250a349285670a8049d05) - Allow customzing the trackio color palette. Thanks @abidlabs!
|
| 22 |
+
|
| 23 |
+
## 0.8.1
|
| 24 |
+
|
| 25 |
+
### Features
|
| 26 |
+
|
| 27 |
+
- [#336](https://github.com/gradio-app/trackio/pull/336) [`5f9f51d`](https://github.com/gradio-app/trackio/commit/5f9f51dac8677f240d7c42c3e3b2660a22aee138) - Support a list of `Trackio.Image` in a `trackio.Table` cell. Thanks @abidlabs!
|
| 28 |
+
|
| 29 |
+
## 0.8.0
|
| 30 |
+
|
| 31 |
+
### Features
|
| 32 |
+
|
| 33 |
+
- [#331](https://github.com/gradio-app/trackio/pull/331) [`2c02d0f`](https://github.com/gradio-app/trackio/commit/2c02d0fd0a5824160528782402bb0dd4083396d5) - Truncate table string values that are greater than 250 characters (configuirable via env variable). Thanks @abidlabs!
|
| 34 |
+
- [#324](https://github.com/gradio-app/trackio/pull/324) [`50b2122`](https://github.com/gradio-app/trackio/commit/50b2122e7965ac82a72e6cb3b7d048bc10a2a6b1) - Add log y-axis functionality to UI. Thanks @abidlabs!
|
| 35 |
+
- [#326](https://github.com/gradio-app/trackio/pull/326) [`61dc1f4`](https://github.com/gradio-app/trackio/commit/61dc1f40af2f545f8e70395ddf0dbb8aee6b60d5) - Fix: improve table rendering for metrics in Trackio Dashboard. Thanks @vigneshwaran!
|
| 36 |
+
- [#328](https://github.com/gradio-app/trackio/pull/328) [`6857cbb`](https://github.com/gradio-app/trackio/commit/6857cbbe557a59a4642f210ec42566d108294e63) - Support trackio.Table with trackio.Image columns. Thanks @abidlabs!
|
| 37 |
+
|
| 38 |
## 0.7.0
|
| 39 |
|
| 40 |
### Features
|
__init__.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import hashlib
|
| 2 |
import json
|
| 3 |
import logging
|
| 4 |
import os
|
|
@@ -7,20 +6,20 @@ import webbrowser
|
|
| 7 |
from pathlib import Path
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
-
from gradio.blocks import BUILT_IN_THEMES
|
| 11 |
-
from gradio.themes import Default as DefaultTheme
|
| 12 |
from gradio.themes import ThemeClass
|
|
|
|
| 13 |
from gradio_client import Client
|
| 14 |
from huggingface_hub import SpaceStorage
|
| 15 |
|
| 16 |
from trackio import context_vars, deploy, utils
|
|
|
|
| 17 |
from trackio.histogram import Histogram
|
| 18 |
from trackio.imports import import_csv, import_tf_events
|
| 19 |
from trackio.media import TrackioAudio, TrackioImage, TrackioVideo
|
| 20 |
from trackio.run import Run
|
| 21 |
from trackio.sqlite_storage import SQLiteStorage
|
| 22 |
from trackio.table import Table
|
| 23 |
-
from trackio.ui.main import demo
|
| 24 |
from trackio.utils import TRACKIO_DIR, TRACKIO_LOGO_DIR
|
| 25 |
|
| 26 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
@@ -41,6 +40,8 @@ __all__ = [
|
|
| 41 |
"log",
|
| 42 |
"finish",
|
| 43 |
"show",
|
|
|
|
|
|
|
| 44 |
"import_csv",
|
| 45 |
"import_tf_events",
|
| 46 |
"Image",
|
|
@@ -140,7 +141,9 @@ def init(
|
|
| 140 |
if url is None:
|
| 141 |
if space_id is None:
|
| 142 |
_, url, share_url = demo.launch(
|
| 143 |
-
|
|
|
|
|
|
|
| 144 |
inline=False,
|
| 145 |
quiet=True,
|
| 146 |
prevent_thread_lock=True,
|
|
@@ -169,7 +172,7 @@ def init(
|
|
| 169 |
if utils.is_in_notebook() and embed:
|
| 170 |
base_url = share_url + "/" if share_url else url
|
| 171 |
full_url = utils.get_full_url(
|
| 172 |
-
base_url, project=project, write_token=demo.write_token
|
| 173 |
)
|
| 174 |
utils.embed_url_in_notebook(full_url)
|
| 175 |
else:
|
|
@@ -261,10 +264,59 @@ def finish():
|
|
| 261 |
run.finish()
|
| 262 |
|
| 263 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
def show(
|
| 265 |
project: str | None = None,
|
|
|
|
| 266 |
theme: str | ThemeClass | None = None,
|
| 267 |
mcp_server: bool | None = None,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
):
|
| 269 |
"""
|
| 270 |
Launches the Trackio dashboard.
|
|
@@ -284,31 +336,32 @@ def show(
|
|
| 284 |
functions will be added as MCP tools. If `None` (default behavior), then the
|
| 285 |
`GRADIO_MCP_SERVER` environment variable will be used to determine if the
|
| 286 |
MCP server should be enabled (which is `"True"` on Hugging Face Spaces).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
"""
|
| 288 |
-
|
|
|
|
| 289 |
|
| 290 |
-
|
| 291 |
-
# TODO: It's a little hacky to reproduce this theme-setting logic from Gradio Blocks,
|
| 292 |
-
# but in Gradio 6.0, the theme will be set in `launch()` instead, which means that we
|
| 293 |
-
# will be able to remove this code.
|
| 294 |
-
if isinstance(theme, str):
|
| 295 |
-
if theme.lower() in BUILT_IN_THEMES:
|
| 296 |
-
theme = BUILT_IN_THEMES[theme.lower()]
|
| 297 |
-
else:
|
| 298 |
-
try:
|
| 299 |
-
theme = ThemeClass.from_hub(theme)
|
| 300 |
-
except Exception as e:
|
| 301 |
-
warnings.warn(f"Cannot load {theme}. Caught Exception: {str(e)}")
|
| 302 |
-
theme = DefaultTheme()
|
| 303 |
-
if not isinstance(theme, ThemeClass):
|
| 304 |
-
warnings.warn("Theme should be a class loaded from gradio.themes")
|
| 305 |
-
theme = DefaultTheme()
|
| 306 |
-
demo.theme: ThemeClass = theme
|
| 307 |
-
demo.theme_css = theme._get_theme_css()
|
| 308 |
-
demo.stylesheets = theme._stylesheets
|
| 309 |
-
theme_hasher = hashlib.sha256()
|
| 310 |
-
theme_hasher.update(demo.theme_css.encode("utf-8"))
|
| 311 |
-
demo.theme_hash = theme_hasher.hexdigest()
|
| 312 |
|
| 313 |
_mcp_server = (
|
| 314 |
mcp_server
|
|
@@ -316,24 +369,33 @@ def show(
|
|
| 316 |
else os.environ.get("GRADIO_MCP_SERVER", "False") == "True"
|
| 317 |
)
|
| 318 |
|
| 319 |
-
|
| 320 |
-
|
|
|
|
|
|
|
| 321 |
quiet=True,
|
| 322 |
inline=False,
|
| 323 |
prevent_thread_lock=True,
|
| 324 |
favicon_path=TRACKIO_LOGO_DIR / "trackio_logo_light.png",
|
| 325 |
allowed_paths=[TRACKIO_LOGO_DIR, TRACKIO_DIR],
|
| 326 |
mcp_server=_mcp_server,
|
|
|
|
| 327 |
)
|
| 328 |
|
| 329 |
base_url = share_url + "/" if share_url else url
|
| 330 |
full_url = utils.get_full_url(
|
| 331 |
-
base_url, project=project, write_token=demo.write_token
|
| 332 |
)
|
| 333 |
|
| 334 |
if not utils.is_in_notebook():
|
| 335 |
print(f"* Trackio UI launched at: {full_url}")
|
| 336 |
-
|
| 337 |
-
|
|
|
|
| 338 |
else:
|
| 339 |
utils.embed_url_in_notebook(full_url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
import logging
|
| 3 |
import os
|
|
|
|
| 6 |
from pathlib import Path
|
| 7 |
from typing import Any
|
| 8 |
|
|
|
|
|
|
|
| 9 |
from gradio.themes import ThemeClass
|
| 10 |
+
from gradio.utils import TupleNoPrint
|
| 11 |
from gradio_client import Client
|
| 12 |
from huggingface_hub import SpaceStorage
|
| 13 |
|
| 14 |
from trackio import context_vars, deploy, utils
|
| 15 |
+
from trackio.deploy import sync
|
| 16 |
from trackio.histogram import Histogram
|
| 17 |
from trackio.imports import import_csv, import_tf_events
|
| 18 |
from trackio.media import TrackioAudio, TrackioImage, TrackioVideo
|
| 19 |
from trackio.run import Run
|
| 20 |
from trackio.sqlite_storage import SQLiteStorage
|
| 21 |
from trackio.table import Table
|
| 22 |
+
from trackio.ui.main import CSS, HEAD, demo
|
| 23 |
from trackio.utils import TRACKIO_DIR, TRACKIO_LOGO_DIR
|
| 24 |
|
| 25 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
|
|
| 40 |
"log",
|
| 41 |
"finish",
|
| 42 |
"show",
|
| 43 |
+
"sync",
|
| 44 |
+
"delete_project",
|
| 45 |
"import_csv",
|
| 46 |
"import_tf_events",
|
| 47 |
"Image",
|
|
|
|
| 141 |
if url is None:
|
| 142 |
if space_id is None:
|
| 143 |
_, url, share_url = demo.launch(
|
| 144 |
+
css=CSS,
|
| 145 |
+
head=HEAD,
|
| 146 |
+
footer_links=["gradio", "settings"],
|
| 147 |
inline=False,
|
| 148 |
quiet=True,
|
| 149 |
prevent_thread_lock=True,
|
|
|
|
| 172 |
if utils.is_in_notebook() and embed:
|
| 173 |
base_url = share_url + "/" if share_url else url
|
| 174 |
full_url = utils.get_full_url(
|
| 175 |
+
base_url, project=project, write_token=demo.write_token, footer=True
|
| 176 |
)
|
| 177 |
utils.embed_url_in_notebook(full_url)
|
| 178 |
else:
|
|
|
|
| 264 |
run.finish()
|
| 265 |
|
| 266 |
|
| 267 |
+
def delete_project(project: str, force: bool = False) -> bool:
|
| 268 |
+
"""
|
| 269 |
+
Deletes a project by removing its local SQLite database.
|
| 270 |
+
|
| 271 |
+
Args:
|
| 272 |
+
project (`str`):
|
| 273 |
+
The name of the project to delete.
|
| 274 |
+
force (`bool`, *optional*, defaults to `False`):
|
| 275 |
+
If `True`, deletes the project without prompting for confirmation.
|
| 276 |
+
If `False`, prompts the user to confirm before deleting.
|
| 277 |
+
|
| 278 |
+
Returns:
|
| 279 |
+
`bool`: `True` if the project was deleted, `False` otherwise.
|
| 280 |
+
"""
|
| 281 |
+
db_path = SQLiteStorage.get_project_db_path(project)
|
| 282 |
+
|
| 283 |
+
if not db_path.exists():
|
| 284 |
+
print(f"* Project '{project}' does not exist.")
|
| 285 |
+
return False
|
| 286 |
+
|
| 287 |
+
if not force:
|
| 288 |
+
response = input(
|
| 289 |
+
f"Are you sure you want to delete project '{project}'? "
|
| 290 |
+
f"This will permanently delete all runs and metrics. (y/N): "
|
| 291 |
+
)
|
| 292 |
+
if response.lower() not in ["y", "yes"]:
|
| 293 |
+
print("* Deletion cancelled.")
|
| 294 |
+
return False
|
| 295 |
+
|
| 296 |
+
try:
|
| 297 |
+
db_path.unlink()
|
| 298 |
+
|
| 299 |
+
for suffix in ("-wal", "-shm"):
|
| 300 |
+
sidecar = Path(str(db_path) + suffix)
|
| 301 |
+
if sidecar.exists():
|
| 302 |
+
sidecar.unlink()
|
| 303 |
+
|
| 304 |
+
print(f"* Project '{project}' has been deleted.")
|
| 305 |
+
return True
|
| 306 |
+
except Exception as e:
|
| 307 |
+
print(f"* Error deleting project '{project}': {e}")
|
| 308 |
+
return False
|
| 309 |
+
|
| 310 |
+
|
| 311 |
def show(
|
| 312 |
project: str | None = None,
|
| 313 |
+
*,
|
| 314 |
theme: str | ThemeClass | None = None,
|
| 315 |
mcp_server: bool | None = None,
|
| 316 |
+
footer: bool = True,
|
| 317 |
+
color_palette: list[str] | None = None,
|
| 318 |
+
open_browser: bool = True,
|
| 319 |
+
block_thread: bool | None = None,
|
| 320 |
):
|
| 321 |
"""
|
| 322 |
Launches the Trackio dashboard.
|
|
|
|
| 336 |
functions will be added as MCP tools. If `None` (default behavior), then the
|
| 337 |
`GRADIO_MCP_SERVER` environment variable will be used to determine if the
|
| 338 |
MCP server should be enabled (which is `"True"` on Hugging Face Spaces).
|
| 339 |
+
footer (`bool`, *optional*, defaults to `True`):
|
| 340 |
+
Whether to show the Gradio footer. When `False`, the footer will be hidden.
|
| 341 |
+
This can also be controlled via the `footer` query parameter in the URL.
|
| 342 |
+
color_palette (`list[str]`, *optional*):
|
| 343 |
+
A list of hex color codes to use for plot lines. If not provided, the
|
| 344 |
+
`TRACKIO_COLOR_PALETTE` environment variable will be used (comma-separated
|
| 345 |
+
hex codes), or if that is not set, the default color palette will be used.
|
| 346 |
+
Example: `['#FF0000', '#00FF00', '#0000FF']`
|
| 347 |
+
open_browser (`bool`, *optional*, defaults to `True`):
|
| 348 |
+
If `True` and not in a notebook, a new browser tab will be opened with the dashboard.
|
| 349 |
+
If `False`, the browser will not be opened.
|
| 350 |
+
block_thread (`bool`, *optional*):
|
| 351 |
+
If `True`, the main thread will be blocked until the dashboard is closed.
|
| 352 |
+
If `None` (default behavior), then the main thread will not be blocked if the
|
| 353 |
+
dashboard is launched in a notebook, otherwise the main thread will be blocked.
|
| 354 |
+
|
| 355 |
+
Returns:
|
| 356 |
+
`app`: The Gradio app object corresponding to the dashboard launched by Trackio.
|
| 357 |
+
`url`: The local URL of the dashboard.
|
| 358 |
+
`share_url`: The public share URL of the dashboard.
|
| 359 |
+
`full_url`: The full URL of the dashboard including the write token (will use the public share URL if launched publicly, otherwise the local URL).
|
| 360 |
"""
|
| 361 |
+
if color_palette is not None:
|
| 362 |
+
os.environ["TRACKIO_COLOR_PALETTE"] = ",".join(color_palette)
|
| 363 |
|
| 364 |
+
theme = theme or os.environ.get("TRACKIO_THEME", DEFAULT_THEME)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
|
| 366 |
_mcp_server = (
|
| 367 |
mcp_server
|
|
|
|
| 369 |
else os.environ.get("GRADIO_MCP_SERVER", "False") == "True"
|
| 370 |
)
|
| 371 |
|
| 372 |
+
app, url, share_url = demo.launch(
|
| 373 |
+
css=CSS,
|
| 374 |
+
head=HEAD,
|
| 375 |
+
footer_links=["gradio", "settings"] + (["api"] if _mcp_server else []),
|
| 376 |
quiet=True,
|
| 377 |
inline=False,
|
| 378 |
prevent_thread_lock=True,
|
| 379 |
favicon_path=TRACKIO_LOGO_DIR / "trackio_logo_light.png",
|
| 380 |
allowed_paths=[TRACKIO_LOGO_DIR, TRACKIO_DIR],
|
| 381 |
mcp_server=_mcp_server,
|
| 382 |
+
theme=theme,
|
| 383 |
)
|
| 384 |
|
| 385 |
base_url = share_url + "/" if share_url else url
|
| 386 |
full_url = utils.get_full_url(
|
| 387 |
+
base_url, project=project, write_token=demo.write_token, footer=footer
|
| 388 |
)
|
| 389 |
|
| 390 |
if not utils.is_in_notebook():
|
| 391 |
print(f"* Trackio UI launched at: {full_url}")
|
| 392 |
+
if open_browser:
|
| 393 |
+
webbrowser.open(full_url)
|
| 394 |
+
block_thread = block_thread if block_thread is not None else True
|
| 395 |
else:
|
| 396 |
utils.embed_url_in_notebook(full_url)
|
| 397 |
+
block_thread = block_thread if block_thread is not None else False
|
| 398 |
+
|
| 399 |
+
if block_thread:
|
| 400 |
+
utils.block_main_thread_until_keyboard_interrupt()
|
| 401 |
+
return TupleNoPrint((demo, url, share_url, full_url))
|
__pycache__/__init__.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/__init__.cpython-310.pyc and b/__pycache__/__init__.cpython-310.pyc differ
|
|
|
__pycache__/cli.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/cli.cpython-310.pyc and b/__pycache__/cli.cpython-310.pyc differ
|
|
|
__pycache__/commit_scheduler.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/commit_scheduler.cpython-310.pyc and b/__pycache__/commit_scheduler.cpython-310.pyc differ
|
|
|
__pycache__/context_vars.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/context_vars.cpython-310.pyc and b/__pycache__/context_vars.cpython-310.pyc differ
|
|
|
__pycache__/deploy.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/deploy.cpython-310.pyc and b/__pycache__/deploy.cpython-310.pyc differ
|
|
|
__pycache__/dummy_commit_scheduler.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/dummy_commit_scheduler.cpython-310.pyc and b/__pycache__/dummy_commit_scheduler.cpython-310.pyc differ
|
|
|
__pycache__/histogram.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/histogram.cpython-310.pyc and b/__pycache__/histogram.cpython-310.pyc differ
|
|
|
__pycache__/imports.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/imports.cpython-310.pyc and b/__pycache__/imports.cpython-310.pyc differ
|
|
|
__pycache__/run.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/run.cpython-310.pyc and b/__pycache__/run.cpython-310.pyc differ
|
|
|
__pycache__/sqlite_storage.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/sqlite_storage.cpython-310.pyc and b/__pycache__/sqlite_storage.cpython-310.pyc differ
|
|
|
__pycache__/table.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/table.cpython-310.pyc and b/__pycache__/table.cpython-310.pyc differ
|
|
|
__pycache__/typehints.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/typehints.cpython-310.pyc and b/__pycache__/typehints.cpython-310.pyc differ
|
|
|
__pycache__/utils.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/utils.cpython-310.pyc and b/__pycache__/utils.cpython-310.pyc differ
|
|
|
cli.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import argparse
|
| 2 |
|
| 3 |
-
from trackio import show
|
| 4 |
|
| 5 |
|
| 6 |
def main():
|
|
@@ -24,11 +24,67 @@ def main():
|
|
| 24 |
action="store_true",
|
| 25 |
help="Enable MCP server functionality. The Trackio dashboard will be set up as an MCP server and certain functions will be exposed as MCP tools.",
|
| 26 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
args = parser.parse_args()
|
| 29 |
|
| 30 |
if args.command == "show":
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
else:
|
| 33 |
parser.print_help()
|
| 34 |
|
|
|
|
| 1 |
import argparse
|
| 2 |
|
| 3 |
+
from trackio import show, sync
|
| 4 |
|
| 5 |
|
| 6 |
def main():
|
|
|
|
| 24 |
action="store_true",
|
| 25 |
help="Enable MCP server functionality. The Trackio dashboard will be set up as an MCP server and certain functions will be exposed as MCP tools.",
|
| 26 |
)
|
| 27 |
+
ui_parser.add_argument(
|
| 28 |
+
"--footer",
|
| 29 |
+
action="store_true",
|
| 30 |
+
default=True,
|
| 31 |
+
help="Show the Gradio footer. Use --no-footer to hide it.",
|
| 32 |
+
)
|
| 33 |
+
ui_parser.add_argument(
|
| 34 |
+
"--no-footer",
|
| 35 |
+
dest="footer",
|
| 36 |
+
action="store_false",
|
| 37 |
+
help="Hide the Gradio footer.",
|
| 38 |
+
)
|
| 39 |
+
ui_parser.add_argument(
|
| 40 |
+
"--color-palette",
|
| 41 |
+
required=False,
|
| 42 |
+
help="Comma-separated list of hex color codes for plot lines (e.g. '#FF0000,#00FF00,#0000FF'). If not provided, the TRACKIO_COLOR_PALETTE environment variable will be used, or the default palette if not set.",
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
sync_parser = subparsers.add_parser(
|
| 46 |
+
"sync",
|
| 47 |
+
help="Sync a local project's database to a Hugging Face Space. If the Space does not exist, it will be created.",
|
| 48 |
+
)
|
| 49 |
+
sync_parser.add_argument(
|
| 50 |
+
"--project", required=True, help="The name of the local project."
|
| 51 |
+
)
|
| 52 |
+
sync_parser.add_argument(
|
| 53 |
+
"--space-id",
|
| 54 |
+
required=True,
|
| 55 |
+
help="The Hugging Face Space ID where the project will be synced (e.g. username/space_id).",
|
| 56 |
+
)
|
| 57 |
+
sync_parser.add_argument(
|
| 58 |
+
"--private",
|
| 59 |
+
action="store_true",
|
| 60 |
+
help="Make the Hugging Face Space private if creating a new Space. By default, the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.",
|
| 61 |
+
)
|
| 62 |
+
sync_parser.add_argument(
|
| 63 |
+
"--force",
|
| 64 |
+
action="store_true",
|
| 65 |
+
help="Overwrite the existing database without prompting for confirmation.",
|
| 66 |
+
)
|
| 67 |
|
| 68 |
args = parser.parse_args()
|
| 69 |
|
| 70 |
if args.command == "show":
|
| 71 |
+
color_palette = None
|
| 72 |
+
if args.color_palette:
|
| 73 |
+
color_palette = [color.strip() for color in args.color_palette.split(",")]
|
| 74 |
+
show(
|
| 75 |
+
project=args.project,
|
| 76 |
+
theme=args.theme,
|
| 77 |
+
mcp_server=args.mcp_server,
|
| 78 |
+
footer=args.footer,
|
| 79 |
+
color_palette=color_palette,
|
| 80 |
+
)
|
| 81 |
+
elif args.command == "sync":
|
| 82 |
+
sync(
|
| 83 |
+
project=args.project,
|
| 84 |
+
space_id=args.space_id,
|
| 85 |
+
private=args.private,
|
| 86 |
+
force=args.force,
|
| 87 |
+
)
|
| 88 |
else:
|
| 89 |
parser.print_help()
|
| 90 |
|
deploy.py
CHANGED
|
@@ -14,6 +14,7 @@ from requests import HTTPError
|
|
| 14 |
|
| 15 |
import trackio
|
| 16 |
from trackio.sqlite_storage import SQLiteStorage
|
|
|
|
| 17 |
|
| 18 |
SPACE_HOST_URL = "https://{user_name}-{space_name}.hf.space/"
|
| 19 |
SPACE_URL = "https://huggingface.co/spaces/{space_id}"
|
|
@@ -154,6 +155,8 @@ trackio.show()"""
|
|
| 154 |
if theme := os.environ.get("TRACKIO_THEME"):
|
| 155 |
huggingface_hub.add_space_variable(space_id, "TRACKIO_THEME", theme)
|
| 156 |
|
|
|
|
|
|
|
| 157 |
|
| 158 |
def create_space_if_not_exists(
|
| 159 |
space_id: str,
|
|
@@ -229,30 +232,75 @@ def wait_until_space_exists(
|
|
| 229 |
Args:
|
| 230 |
space_id: The ID of the Space to wait for.
|
| 231 |
"""
|
|
|
|
| 232 |
delay = 1
|
| 233 |
-
for _ in range(
|
| 234 |
try:
|
| 235 |
-
|
| 236 |
return
|
| 237 |
-
except (
|
| 238 |
time.sleep(delay)
|
| 239 |
-
delay = min(delay * 2,
|
| 240 |
raise TimeoutError("Waiting for space to exist took longer than expected")
|
| 241 |
|
| 242 |
|
| 243 |
-
def upload_db_to_space(project: str, space_id: str) -> None:
|
| 244 |
"""
|
| 245 |
-
Uploads the database of a local Trackio project to a Hugging Face Space.
|
|
|
|
|
|
|
| 246 |
|
| 247 |
Args:
|
| 248 |
project: The name of the project to upload.
|
| 249 |
space_id: The ID of the Space to upload to.
|
|
|
|
| 250 |
"""
|
| 251 |
db_path = SQLiteStorage.get_project_db_path(project)
|
| 252 |
-
client = Client(space_id, verbose=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
client.predict(
|
| 254 |
api_name="/upload_db_to_space",
|
| 255 |
project=project,
|
| 256 |
uploaded_db=handle_file(db_path),
|
| 257 |
hf_token=huggingface_hub.utils.get_token(),
|
| 258 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
import trackio
|
| 16 |
from trackio.sqlite_storage import SQLiteStorage
|
| 17 |
+
from trackio.utils import preprocess_space_and_dataset_ids
|
| 18 |
|
| 19 |
SPACE_HOST_URL = "https://{user_name}-{space_name}.hf.space/"
|
| 20 |
SPACE_URL = "https://huggingface.co/spaces/{space_id}"
|
|
|
|
| 155 |
if theme := os.environ.get("TRACKIO_THEME"):
|
| 156 |
huggingface_hub.add_space_variable(space_id, "TRACKIO_THEME", theme)
|
| 157 |
|
| 158 |
+
huggingface_hub.add_space_variable(space_id, "GRADIO_MCP_SERVER", "True")
|
| 159 |
+
|
| 160 |
|
| 161 |
def create_space_if_not_exists(
|
| 162 |
space_id: str,
|
|
|
|
| 232 |
Args:
|
| 233 |
space_id: The ID of the Space to wait for.
|
| 234 |
"""
|
| 235 |
+
hf_api = huggingface_hub.HfApi()
|
| 236 |
delay = 1
|
| 237 |
+
for _ in range(30):
|
| 238 |
try:
|
| 239 |
+
hf_api.space_info(space_id)
|
| 240 |
return
|
| 241 |
+
except (huggingface_hub.utils.HfHubHTTPError, ReadTimeout):
|
| 242 |
time.sleep(delay)
|
| 243 |
+
delay = min(delay * 2, 60)
|
| 244 |
raise TimeoutError("Waiting for space to exist took longer than expected")
|
| 245 |
|
| 246 |
|
| 247 |
+
def upload_db_to_space(project: str, space_id: str, force: bool = False) -> None:
|
| 248 |
"""
|
| 249 |
+
Uploads the database of a local Trackio project to a Hugging Face Space. It
|
| 250 |
+
uses the Gradio Client to upload since we do not want to trigger a new build
|
| 251 |
+
of the Space, which would happen if we used `huggingface_hub.upload_file`.
|
| 252 |
|
| 253 |
Args:
|
| 254 |
project: The name of the project to upload.
|
| 255 |
space_id: The ID of the Space to upload to.
|
| 256 |
+
force: If True, overwrite existing database without prompting. If False, prompt for confirmation.
|
| 257 |
"""
|
| 258 |
db_path = SQLiteStorage.get_project_db_path(project)
|
| 259 |
+
client = Client(space_id, verbose=False, httpx_kwargs={"timeout": 90})
|
| 260 |
+
|
| 261 |
+
if not force:
|
| 262 |
+
try:
|
| 263 |
+
existing_projects = client.predict(api_name="/get_all_projects")
|
| 264 |
+
if project in existing_projects:
|
| 265 |
+
response = input(
|
| 266 |
+
f"Database for project '{project}' already exists on Space '{space_id}'. "
|
| 267 |
+
f"Overwrite it? (y/N): "
|
| 268 |
+
)
|
| 269 |
+
if response.lower() not in ["y", "yes"]:
|
| 270 |
+
print("* Upload cancelled.")
|
| 271 |
+
return
|
| 272 |
+
except Exception as e:
|
| 273 |
+
print(f"* Warning: Could not check if project exists on Space: {e}")
|
| 274 |
+
print("* Proceeding with upload...")
|
| 275 |
+
|
| 276 |
client.predict(
|
| 277 |
api_name="/upload_db_to_space",
|
| 278 |
project=project,
|
| 279 |
uploaded_db=handle_file(db_path),
|
| 280 |
hf_token=huggingface_hub.utils.get_token(),
|
| 281 |
)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def sync(
|
| 285 |
+
project: str, space_id: str, private: bool | None = None, force: bool = False
|
| 286 |
+
) -> None:
|
| 287 |
+
"""
|
| 288 |
+
Syncs a local Trackio project's database to a Hugging Face Space.
|
| 289 |
+
If the Space does not exist, it will be created.
|
| 290 |
+
|
| 291 |
+
Args:
|
| 292 |
+
project (`str`): The name of the project to upload.
|
| 293 |
+
space_id (`str`): The ID of the Space to upload to (e.g., `"username/space_id"`).
|
| 294 |
+
private (`bool`, *optional*):
|
| 295 |
+
Whether to make the Space private. If None (default), the repo will be
|
| 296 |
+
public unless the organization's default is private. This value is ignored
|
| 297 |
+
if the repo already exists.
|
| 298 |
+
force (`bool`, *optional*, defaults to `False`):
|
| 299 |
+
If `True`, overwrite the existing database without prompting for confirmation.
|
| 300 |
+
If `False`, prompt the user before overwriting an existing database.
|
| 301 |
+
"""
|
| 302 |
+
space_id, _ = preprocess_space_and_dataset_ids(space_id, None)
|
| 303 |
+
create_space_if_not_exists(space_id, private=private)
|
| 304 |
+
wait_until_space_exists(space_id)
|
| 305 |
+
upload_db_to_space(project, space_id, force=force)
|
| 306 |
+
print(f"Synced successfully to space: {SPACE_URL.format(space_id=space_id)}")
|
imports.py
CHANGED
|
@@ -14,6 +14,7 @@ def import_csv(
|
|
| 14 |
space_id: str | None = None,
|
| 15 |
dataset_id: str | None = None,
|
| 16 |
private: bool | None = None,
|
|
|
|
| 17 |
) -> None:
|
| 18 |
"""
|
| 19 |
Imports a CSV file into a Trackio project. The CSV file must contain a `"step"`
|
|
@@ -143,7 +144,7 @@ def import_csv(
|
|
| 143 |
space_id=space_id, dataset_id=dataset_id, private=private
|
| 144 |
)
|
| 145 |
deploy.wait_until_space_exists(space_id=space_id)
|
| 146 |
-
deploy.upload_db_to_space(project=project, space_id=space_id)
|
| 147 |
print(
|
| 148 |
f"* View dashboard by going to: {deploy.SPACE_URL.format(space_id=space_id)}"
|
| 149 |
)
|
|
@@ -156,6 +157,7 @@ def import_tf_events(
|
|
| 156 |
space_id: str | None = None,
|
| 157 |
dataset_id: str | None = None,
|
| 158 |
private: bool | None = None,
|
|
|
|
| 159 |
) -> None:
|
| 160 |
"""
|
| 161 |
Imports TensorFlow Events files from a directory into a Trackio project. Each
|
|
@@ -296,7 +298,7 @@ def import_tf_events(
|
|
| 296 |
space_id, dataset_id=dataset_id, private=private
|
| 297 |
)
|
| 298 |
deploy.wait_until_space_exists(space_id)
|
| 299 |
-
deploy.upload_db_to_space(project, space_id)
|
| 300 |
print(
|
| 301 |
f"* View dashboard by going to: {deploy.SPACE_URL.format(space_id=space_id)}"
|
| 302 |
)
|
|
|
|
| 14 |
space_id: str | None = None,
|
| 15 |
dataset_id: str | None = None,
|
| 16 |
private: bool | None = None,
|
| 17 |
+
force: bool = False,
|
| 18 |
) -> None:
|
| 19 |
"""
|
| 20 |
Imports a CSV file into a Trackio project. The CSV file must contain a `"step"`
|
|
|
|
| 144 |
space_id=space_id, dataset_id=dataset_id, private=private
|
| 145 |
)
|
| 146 |
deploy.wait_until_space_exists(space_id=space_id)
|
| 147 |
+
deploy.upload_db_to_space(project=project, space_id=space_id, force=force)
|
| 148 |
print(
|
| 149 |
f"* View dashboard by going to: {deploy.SPACE_URL.format(space_id=space_id)}"
|
| 150 |
)
|
|
|
|
| 157 |
space_id: str | None = None,
|
| 158 |
dataset_id: str | None = None,
|
| 159 |
private: bool | None = None,
|
| 160 |
+
force: bool = False,
|
| 161 |
) -> None:
|
| 162 |
"""
|
| 163 |
Imports TensorFlow Events files from a directory into a Trackio project. Each
|
|
|
|
| 298 |
space_id, dataset_id=dataset_id, private=private
|
| 299 |
)
|
| 300 |
deploy.wait_until_space_exists(space_id)
|
| 301 |
+
deploy.upload_db_to_space(project, space_id, force=force)
|
| 302 |
print(
|
| 303 |
f"* View dashboard by going to: {deploy.SPACE_URL.format(space_id=space_id)}"
|
| 304 |
)
|
media/__pycache__/__init__.cpython-310.pyc
CHANGED
|
Binary files a/media/__pycache__/__init__.cpython-310.pyc and b/media/__pycache__/__init__.cpython-310.pyc differ
|
|
|
media/__pycache__/audio_writer.cpython-310.pyc
CHANGED
|
Binary files a/media/__pycache__/audio_writer.cpython-310.pyc and b/media/__pycache__/audio_writer.cpython-310.pyc differ
|
|
|
media/__pycache__/file_storage.cpython-310.pyc
CHANGED
|
Binary files a/media/__pycache__/file_storage.cpython-310.pyc and b/media/__pycache__/file_storage.cpython-310.pyc differ
|
|
|
media/__pycache__/media.cpython-310.pyc
CHANGED
|
Binary files a/media/__pycache__/media.cpython-310.pyc and b/media/__pycache__/media.cpython-310.pyc differ
|
|
|
media/__pycache__/utils.cpython-310.pyc
CHANGED
|
Binary files a/media/__pycache__/utils.cpython-310.pyc and b/media/__pycache__/utils.cpython-310.pyc differ
|
|
|
media/__pycache__/video_writer.cpython-310.pyc
CHANGED
|
Binary files a/media/__pycache__/video_writer.cpython-310.pyc and b/media/__pycache__/video_writer.cpython-310.pyc differ
|
|
|
package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "trackio",
|
| 3 |
-
"version": "0.
|
| 4 |
"description": "",
|
| 5 |
"python": "true"
|
| 6 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "trackio",
|
| 3 |
+
"version": "0.10.0",
|
| 4 |
"description": "",
|
| 5 |
"python": "true"
|
| 6 |
}
|
run.py
CHANGED
|
@@ -12,6 +12,7 @@ from trackio.media import TrackioMedia
|
|
| 12 |
from trackio.sqlite_storage import SQLiteStorage
|
| 13 |
from trackio.table import Table
|
| 14 |
from trackio.typehints import LogEntry, UploadEntry
|
|
|
|
| 15 |
|
| 16 |
BATCH_SEND_INTERVAL = 0.5
|
| 17 |
|
|
@@ -62,16 +63,13 @@ class Run:
|
|
| 62 |
def _get_username(self) -> str | None:
|
| 63 |
"""Get the current HuggingFace username if logged in, otherwise None."""
|
| 64 |
try:
|
| 65 |
-
|
| 66 |
-
return who["name"] if who else None
|
| 67 |
except Exception:
|
| 68 |
return None
|
| 69 |
|
| 70 |
def _batch_sender(self):
|
| 71 |
"""Send batched logs every BATCH_SEND_INTERVAL."""
|
| 72 |
while not self._stop_flag.is_set() or len(self._queued_logs) > 0:
|
| 73 |
-
# If the stop flag has been set, then just quickly send all
|
| 74 |
-
# the logs and exit.
|
| 75 |
if not self._stop_flag.is_set():
|
| 76 |
time.sleep(BATCH_SEND_INTERVAL)
|
| 77 |
|
|
@@ -112,36 +110,60 @@ class Run:
|
|
| 112 |
|
| 113 |
self._batch_sender()
|
| 114 |
|
| 115 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
"""
|
| 117 |
Serialize media in metrics and upload to space if needed.
|
| 118 |
"""
|
| 119 |
-
|
| 120 |
-
if
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
if isinstance(value, TrackioMedia):
|
| 124 |
-
value._save(self.project, self.name, step)
|
| 125 |
-
serializable_metrics[key] = value._to_dict()
|
| 126 |
-
if self._space_id:
|
| 127 |
-
# Upload local media when deploying to space
|
| 128 |
-
upload_entry: UploadEntry = {
|
| 129 |
-
"project": self.project,
|
| 130 |
-
"run": self.name,
|
| 131 |
-
"step": step,
|
| 132 |
-
"uploaded_file": handle_file(value._get_absolute_file_path()),
|
| 133 |
-
}
|
| 134 |
-
with self._client_lock:
|
| 135 |
-
self._queued_uploads.append(upload_entry)
|
| 136 |
-
else:
|
| 137 |
-
serializable_metrics[key] = value
|
| 138 |
-
return serializable_metrics
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
for
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
def log(self, metrics: dict, step: int | None = None):
|
| 147 |
renamed_keys = []
|
|
@@ -159,9 +181,16 @@ class Run:
|
|
| 159 |
warnings.warn(f"Reserved keys renamed: {renamed_keys} → '__{{key}}'")
|
| 160 |
|
| 161 |
metrics = new_metrics
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
metrics = utils.serialize_values(metrics)
|
| 166 |
|
| 167 |
config_to_log = None
|
|
@@ -184,7 +213,6 @@ class Run:
|
|
| 184 |
"""Cleanup when run is finished."""
|
| 185 |
self._stop_flag.set()
|
| 186 |
|
| 187 |
-
# Wait for the batch sender to finish before joining the client thread.
|
| 188 |
time.sleep(2 * BATCH_SEND_INTERVAL)
|
| 189 |
|
| 190 |
if self._client_thread is not None:
|
|
|
|
| 12 |
from trackio.sqlite_storage import SQLiteStorage
|
| 13 |
from trackio.table import Table
|
| 14 |
from trackio.typehints import LogEntry, UploadEntry
|
| 15 |
+
from trackio.utils import _get_default_namespace
|
| 16 |
|
| 17 |
BATCH_SEND_INTERVAL = 0.5
|
| 18 |
|
|
|
|
| 63 |
def _get_username(self) -> str | None:
|
| 64 |
"""Get the current HuggingFace username if logged in, otherwise None."""
|
| 65 |
try:
|
| 66 |
+
return _get_default_namespace()
|
|
|
|
| 67 |
except Exception:
|
| 68 |
return None
|
| 69 |
|
| 70 |
def _batch_sender(self):
|
| 71 |
"""Send batched logs every BATCH_SEND_INTERVAL."""
|
| 72 |
while not self._stop_flag.is_set() or len(self._queued_logs) > 0:
|
|
|
|
|
|
|
| 73 |
if not self._stop_flag.is_set():
|
| 74 |
time.sleep(BATCH_SEND_INTERVAL)
|
| 75 |
|
|
|
|
| 110 |
|
| 111 |
self._batch_sender()
|
| 112 |
|
| 113 |
+
def _queue_upload(self, file_path, step: int | None):
|
| 114 |
+
"""Queue a media file for upload to space."""
|
| 115 |
+
upload_entry: UploadEntry = {
|
| 116 |
+
"project": self.project,
|
| 117 |
+
"run": self.name,
|
| 118 |
+
"step": step,
|
| 119 |
+
"uploaded_file": handle_file(file_path),
|
| 120 |
+
}
|
| 121 |
+
with self._client_lock:
|
| 122 |
+
self._queued_uploads.append(upload_entry)
|
| 123 |
+
|
| 124 |
+
def _process_media(self, value: TrackioMedia, step: int | None) -> dict:
|
| 125 |
"""
|
| 126 |
Serialize media in metrics and upload to space if needed.
|
| 127 |
"""
|
| 128 |
+
value._save(self.project, self.name, step)
|
| 129 |
+
if self._space_id:
|
| 130 |
+
self._queue_upload(value._get_absolute_file_path(), step)
|
| 131 |
+
return value._to_dict()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
def _scan_and_queue_media_uploads(self, table_dict: dict, step: int | None):
|
| 134 |
+
"""
|
| 135 |
+
Scan a serialized table for media objects and queue them for upload to space.
|
| 136 |
+
"""
|
| 137 |
+
if not self._space_id:
|
| 138 |
+
return
|
| 139 |
+
|
| 140 |
+
table_data = table_dict.get("_value", [])
|
| 141 |
+
for row in table_data:
|
| 142 |
+
for value in row.values():
|
| 143 |
+
if isinstance(value, dict) and value.get("_type") in [
|
| 144 |
+
"trackio.image",
|
| 145 |
+
"trackio.video",
|
| 146 |
+
"trackio.audio",
|
| 147 |
+
]:
|
| 148 |
+
file_path = value.get("file_path")
|
| 149 |
+
if file_path:
|
| 150 |
+
from trackio.utils import MEDIA_DIR
|
| 151 |
+
|
| 152 |
+
absolute_path = MEDIA_DIR / file_path
|
| 153 |
+
self._queue_upload(absolute_path, step)
|
| 154 |
+
elif isinstance(value, list):
|
| 155 |
+
for item in value:
|
| 156 |
+
if isinstance(item, dict) and item.get("_type") in [
|
| 157 |
+
"trackio.image",
|
| 158 |
+
"trackio.video",
|
| 159 |
+
"trackio.audio",
|
| 160 |
+
]:
|
| 161 |
+
file_path = item.get("file_path")
|
| 162 |
+
if file_path:
|
| 163 |
+
from trackio.utils import MEDIA_DIR
|
| 164 |
+
|
| 165 |
+
absolute_path = MEDIA_DIR / file_path
|
| 166 |
+
self._queue_upload(absolute_path, step)
|
| 167 |
|
| 168 |
def log(self, metrics: dict, step: int | None = None):
|
| 169 |
renamed_keys = []
|
|
|
|
| 181 |
warnings.warn(f"Reserved keys renamed: {renamed_keys} → '__{{key}}'")
|
| 182 |
|
| 183 |
metrics = new_metrics
|
| 184 |
+
for key, value in metrics.items():
|
| 185 |
+
if isinstance(value, Table):
|
| 186 |
+
metrics[key] = value._to_dict(
|
| 187 |
+
project=self.project, run=self.name, step=step
|
| 188 |
+
)
|
| 189 |
+
self._scan_and_queue_media_uploads(metrics[key], step)
|
| 190 |
+
elif isinstance(value, Histogram):
|
| 191 |
+
metrics[key] = value._to_dict()
|
| 192 |
+
elif isinstance(value, TrackioMedia):
|
| 193 |
+
metrics[key] = self._process_media(value, step)
|
| 194 |
metrics = utils.serialize_values(metrics)
|
| 195 |
|
| 196 |
config_to_log = None
|
|
|
|
| 213 |
"""Cleanup when run is finished."""
|
| 214 |
self._stop_flag.set()
|
| 215 |
|
|
|
|
| 216 |
time.sleep(2 * BATCH_SEND_INTERVAL)
|
| 217 |
|
| 218 |
if self._client_thread is not None:
|
table.py
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
|
|
| 1 |
from typing import Any, Literal
|
| 2 |
|
| 3 |
from pandas import DataFrame
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class Table:
|
| 7 |
"""
|
| 8 |
-
Initializes a Table object.
|
| 9 |
|
| 10 |
Args:
|
| 11 |
columns (`list[str]`, *optional*):
|
| 12 |
Names of the columns in the table. Optional if `data` is provided. Not
|
| 13 |
expected if `dataframe` is provided. Currently ignored.
|
| 14 |
data (`list[list[Any]]`, *optional*):
|
| 15 |
-
2D row-oriented array of values.
|
|
|
|
| 16 |
dataframe (`pandas.`DataFrame``, *optional*):
|
| 17 |
DataFrame object used to create the table. When set, `data` and `columns`
|
| 18 |
arguments are ignored.
|
|
@@ -40,14 +49,115 @@ class Table:
|
|
| 40 |
):
|
| 41 |
# TODO: implement support for columns, dtype, optional, allow_mixed_types, and log_mode.
|
| 42 |
# for now (like `rows`) they are included for API compat but don't do anything.
|
| 43 |
-
|
| 44 |
if dataframe is None:
|
| 45 |
-
self.data = data
|
| 46 |
else:
|
| 47 |
-
self.data = dataframe
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
return {
|
| 51 |
"_type": self.TYPE,
|
| 52 |
-
"_value":
|
| 53 |
}
|
|
|
|
| 1 |
+
import os
|
| 2 |
from typing import Any, Literal
|
| 3 |
|
| 4 |
from pandas import DataFrame
|
| 5 |
|
| 6 |
+
try:
|
| 7 |
+
from trackio.media.media import TrackioMedia
|
| 8 |
+
from trackio.utils import MEDIA_DIR
|
| 9 |
+
except ImportError:
|
| 10 |
+
from media.media import TrackioMedia
|
| 11 |
+
from utils import MEDIA_DIR
|
| 12 |
+
|
| 13 |
|
| 14 |
class Table:
|
| 15 |
"""
|
| 16 |
+
Initializes a Table object. Tables can be used to log tabular data including images, numbers, and text.
|
| 17 |
|
| 18 |
Args:
|
| 19 |
columns (`list[str]`, *optional*):
|
| 20 |
Names of the columns in the table. Optional if `data` is provided. Not
|
| 21 |
expected if `dataframe` is provided. Currently ignored.
|
| 22 |
data (`list[list[Any]]`, *optional*):
|
| 23 |
+
2D row-oriented array of values. Each value can be: a number, a string (treated as Markdown and truncated if too long),
|
| 24 |
+
or a `Trackio.Image` or list of `Trackio.Image` objects.
|
| 25 |
dataframe (`pandas.`DataFrame``, *optional*):
|
| 26 |
DataFrame object used to create the table. When set, `data` and `columns`
|
| 27 |
arguments are ignored.
|
|
|
|
| 49 |
):
|
| 50 |
# TODO: implement support for columns, dtype, optional, allow_mixed_types, and log_mode.
|
| 51 |
# for now (like `rows`) they are included for API compat but don't do anything.
|
|
|
|
| 52 |
if dataframe is None:
|
| 53 |
+
self.data = DataFrame(data) if data is not None else DataFrame()
|
| 54 |
else:
|
| 55 |
+
self.data = dataframe
|
| 56 |
+
|
| 57 |
+
def _has_media_objects(self, dataframe: DataFrame) -> bool:
|
| 58 |
+
"""Check if dataframe contains any TrackioMedia objects or lists of TrackioMedia objects."""
|
| 59 |
+
for col in dataframe.columns:
|
| 60 |
+
if dataframe[col].apply(lambda x: isinstance(x, TrackioMedia)).any():
|
| 61 |
+
return True
|
| 62 |
+
if (
|
| 63 |
+
dataframe[col]
|
| 64 |
+
.apply(
|
| 65 |
+
lambda x: isinstance(x, list)
|
| 66 |
+
and len(x) > 0
|
| 67 |
+
and isinstance(x[0], TrackioMedia)
|
| 68 |
+
)
|
| 69 |
+
.any()
|
| 70 |
+
):
|
| 71 |
+
return True
|
| 72 |
+
return False
|
| 73 |
+
|
| 74 |
+
def _process_data(self, project: str, run: str, step: int = 0):
|
| 75 |
+
"""Convert dataframe to dict format, processing any TrackioMedia objects if present."""
|
| 76 |
+
df = self.data
|
| 77 |
+
if not self._has_media_objects(df):
|
| 78 |
+
return df.to_dict(orient="records")
|
| 79 |
+
|
| 80 |
+
processed_df = df.copy()
|
| 81 |
+
for col in processed_df.columns:
|
| 82 |
+
for idx in processed_df.index:
|
| 83 |
+
value = processed_df.at[idx, col]
|
| 84 |
+
if isinstance(value, TrackioMedia):
|
| 85 |
+
value._save(project, run, step)
|
| 86 |
+
processed_df.at[idx, col] = value._to_dict()
|
| 87 |
+
if (
|
| 88 |
+
isinstance(value, list)
|
| 89 |
+
and len(value) > 0
|
| 90 |
+
and isinstance(value[0], TrackioMedia)
|
| 91 |
+
):
|
| 92 |
+
[v._save(project, run, step) for v in value]
|
| 93 |
+
processed_df.at[idx, col] = [v._to_dict() for v in value]
|
| 94 |
+
|
| 95 |
+
return processed_df.to_dict(orient="records")
|
| 96 |
+
|
| 97 |
+
@staticmethod
|
| 98 |
+
def to_display_format(table_data: list[dict]) -> list[dict]:
|
| 99 |
+
"""Convert stored table data to display format for UI rendering. Note
|
| 100 |
+
that this does not use the self.data attribute, but instead uses the
|
| 101 |
+
table_data parameter, which is is what the UI receives.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
table_data: List of dictionaries representing table rows (from stored _value)
|
| 105 |
+
|
| 106 |
+
Returns:
|
| 107 |
+
Table data with images converted to markdown syntax and long text truncated.
|
| 108 |
+
"""
|
| 109 |
+
truncate_length = int(os.getenv("TRACKIO_TABLE_TRUNCATE_LENGTH", "250"))
|
| 110 |
+
|
| 111 |
+
def convert_image_to_markdown(image_data: dict) -> str:
|
| 112 |
+
relative_path = image_data.get("file_path", "")
|
| 113 |
+
caption = image_data.get("caption", "")
|
| 114 |
+
absolute_path = MEDIA_DIR / relative_path
|
| 115 |
+
return f'<img src="/gradio_api/file={absolute_path}" alt="{caption}" />'
|
| 116 |
+
|
| 117 |
+
processed_data = []
|
| 118 |
+
for row in table_data:
|
| 119 |
+
processed_row = {}
|
| 120 |
+
for key, value in row.items():
|
| 121 |
+
if isinstance(value, dict) and value.get("_type") == "trackio.image":
|
| 122 |
+
processed_row[key] = convert_image_to_markdown(value)
|
| 123 |
+
elif (
|
| 124 |
+
isinstance(value, list)
|
| 125 |
+
and len(value) > 0
|
| 126 |
+
and isinstance(value[0], dict)
|
| 127 |
+
and value[0].get("_type") == "trackio.image"
|
| 128 |
+
):
|
| 129 |
+
# This assumes that if the first item is an image, all items are images. Ok for now since we don't support mixed types in a single cell.
|
| 130 |
+
processed_row[key] = (
|
| 131 |
+
'<div style="display: flex; gap: 10px;">'
|
| 132 |
+
+ "".join([convert_image_to_markdown(item) for item in value])
|
| 133 |
+
+ "</div>"
|
| 134 |
+
)
|
| 135 |
+
elif isinstance(value, str) and len(value) > truncate_length:
|
| 136 |
+
truncated = value[:truncate_length]
|
| 137 |
+
full_text = value.replace("<", "<").replace(">", ">")
|
| 138 |
+
processed_row[key] = (
|
| 139 |
+
f'<details style="display: inline;">'
|
| 140 |
+
f'<summary style="display: inline; cursor: pointer;">{truncated}…<span><em>(truncated, click to expand)</em></span></summary>'
|
| 141 |
+
f'<div style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px; max-height: 400px; overflow: auto;">'
|
| 142 |
+
f'<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">{full_text}</pre>'
|
| 143 |
+
f"</div>"
|
| 144 |
+
f"</details>"
|
| 145 |
+
)
|
| 146 |
+
else:
|
| 147 |
+
processed_row[key] = value
|
| 148 |
+
processed_data.append(processed_row)
|
| 149 |
+
return processed_data
|
| 150 |
+
|
| 151 |
+
def _to_dict(self, project: str, run: str, step: int = 0):
|
| 152 |
+
"""Convert table to dictionary representation.
|
| 153 |
|
| 154 |
+
Args:
|
| 155 |
+
project: Project name for saving media files
|
| 156 |
+
run: Run name for saving media files
|
| 157 |
+
step: Step number for saving media files
|
| 158 |
+
"""
|
| 159 |
+
data = self._process_data(project, run, step)
|
| 160 |
return {
|
| 161 |
"_type": self.TYPE,
|
| 162 |
+
"_value": data,
|
| 163 |
}
|
ui/__pycache__/__init__.cpython-310.pyc
CHANGED
|
Binary files a/ui/__pycache__/__init__.cpython-310.pyc and b/ui/__pycache__/__init__.cpython-310.pyc differ
|
|
|
ui/__pycache__/fns.cpython-310.pyc
CHANGED
|
Binary files a/ui/__pycache__/fns.cpython-310.pyc and b/ui/__pycache__/fns.cpython-310.pyc differ
|
|
|
ui/__pycache__/main.cpython-310.pyc
CHANGED
|
Binary files a/ui/__pycache__/main.cpython-310.pyc and b/ui/__pycache__/main.cpython-310.pyc differ
|
|
|
ui/__pycache__/run_detail.cpython-310.pyc
CHANGED
|
Binary files a/ui/__pycache__/run_detail.cpython-310.pyc and b/ui/__pycache__/run_detail.cpython-310.pyc differ
|
|
|
ui/__pycache__/runs.cpython-310.pyc
CHANGED
|
Binary files a/ui/__pycache__/runs.cpython-310.pyc and b/ui/__pycache__/runs.cpython-310.pyc differ
|
|
|
ui/fns.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""Shared functions for the Trackio UI."""
|
| 2 |
|
| 3 |
import os
|
|
|
|
| 4 |
|
| 5 |
import gradio as gr
|
| 6 |
import huggingface_hub as hf
|
|
@@ -82,6 +83,7 @@ def update_navbar_value(project_dd, request: gr.Request):
|
|
| 82 |
)
|
| 83 |
|
| 84 |
|
|
|
|
| 85 |
def check_hf_token_has_write_access(hf_token: str | None) -> None:
|
| 86 |
"""
|
| 87 |
Checks to see if the provided hf_token is valid and has write access to the Space
|
|
@@ -137,6 +139,7 @@ def check_hf_token_has_write_access(hf_token: str | None) -> None:
|
|
| 137 |
)
|
| 138 |
|
| 139 |
|
|
|
|
| 140 |
def check_oauth_token_has_write_access(oauth_token: str | None) -> None:
|
| 141 |
"""
|
| 142 |
Checks to see if the oauth token provided via Gradio's OAuth is valid and has write access
|
|
|
|
| 1 |
"""Shared functions for the Trackio UI."""
|
| 2 |
|
| 3 |
import os
|
| 4 |
+
from functools import lru_cache
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import huggingface_hub as hf
|
|
|
|
| 83 |
)
|
| 84 |
|
| 85 |
|
| 86 |
+
@lru_cache(maxsize=32)
|
| 87 |
def check_hf_token_has_write_access(hf_token: str | None) -> None:
|
| 88 |
"""
|
| 89 |
Checks to see if the provided hf_token is valid and has write access to the Space
|
|
|
|
| 139 |
)
|
| 140 |
|
| 141 |
|
| 142 |
+
@lru_cache(maxsize=32)
|
| 143 |
def check_oauth_token_has_write_access(oauth_token: str | None) -> None:
|
| 144 |
"""
|
| 145 |
Checks to see if the oauth token provided via Gradio's OAuth is valid and has write access
|
ui/helpers/__pycache__/run_selection.cpython-310.pyc
CHANGED
|
Binary files a/ui/helpers/__pycache__/run_selection.cpython-310.pyc and b/ui/helpers/__pycache__/run_selection.cpython-310.pyc differ
|
|
|
ui/main.py
CHANGED
|
@@ -99,6 +99,18 @@ def get_runs(project) -> list[str]:
|
|
| 99 |
return SQLiteStorage.get_runs(project)
|
| 100 |
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
def get_available_metrics(project: str, runs: list[str]) -> list[str]:
|
| 103 |
"""Get all available metrics across all runs for x-axis selection."""
|
| 104 |
if not project or not runs:
|
|
@@ -162,9 +174,10 @@ def extract_media(logs: list[dict]) -> dict[str, list[MediaData]]:
|
|
| 162 |
def load_run_data(
|
| 163 |
project: str | None,
|
| 164 |
run: str | None,
|
| 165 |
-
smoothing_granularity: int,
|
| 166 |
-
x_axis: str,
|
| 167 |
-
|
|
|
|
| 168 |
) -> tuple[pd.DataFrame, dict]:
|
| 169 |
if not project or not run:
|
| 170 |
return None, None
|
|
@@ -189,13 +202,26 @@ def load_run_data(
|
|
| 189 |
else:
|
| 190 |
x_column = x_axis
|
| 191 |
|
| 192 |
-
if
|
| 193 |
x_vals = df[x_column]
|
| 194 |
if (x_vals <= 0).any():
|
| 195 |
df[x_column] = np.log10(np.maximum(x_vals, 0) + 1)
|
| 196 |
else:
|
| 197 |
df[x_column] = np.log10(x_vals)
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
if smoothing_granularity > 0:
|
| 200 |
numeric_cols = df.select_dtypes(include="number").columns
|
| 201 |
numeric_cols = [c for c in numeric_cols if c not in utils.RESERVED_KEYS]
|
|
@@ -271,22 +297,6 @@ def toggle_timer(cb_value):
|
|
| 271 |
return gr.Timer(active=False)
|
| 272 |
|
| 273 |
|
| 274 |
-
def upload_db_to_space(
|
| 275 |
-
project: str, uploaded_db: gr.FileData, hf_token: str | None
|
| 276 |
-
) -> None:
|
| 277 |
-
"""
|
| 278 |
-
Uploads the database of a local Trackio project to a Hugging Face Space.
|
| 279 |
-
"""
|
| 280 |
-
fns.check_hf_token_has_write_access(hf_token)
|
| 281 |
-
db_project_path = SQLiteStorage.get_project_db_path(project)
|
| 282 |
-
if os.path.exists(db_project_path):
|
| 283 |
-
raise gr.Error(
|
| 284 |
-
f"Trackio database file already exists for project {project}, cannot overwrite."
|
| 285 |
-
)
|
| 286 |
-
os.makedirs(os.path.dirname(db_project_path), exist_ok=True)
|
| 287 |
-
shutil.copy(uploaded_db["path"], db_project_path)
|
| 288 |
-
|
| 289 |
-
|
| 290 |
def bulk_upload_media(uploads: list[UploadEntry], hf_token: str | None) -> None:
|
| 291 |
"""
|
| 292 |
Uploads media files to a Trackio dashboard. Each entry in the list is a tuple of the project, run, and media file to be uploaded.
|
|
@@ -548,7 +558,7 @@ def create_media_section(media_by_run: dict[str, dict[str, list[MediaData]]]):
|
|
| 548 |
)
|
| 549 |
|
| 550 |
|
| 551 |
-
|
| 552 |
#run-cb .wrap { gap: 2px; }
|
| 553 |
#run-cb .wrap label {
|
| 554 |
line-height: 1;
|
|
@@ -603,9 +613,13 @@ css = """
|
|
| 603 |
gap: 0.25em;
|
| 604 |
margin-bottom: 0.25em;
|
| 605 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
"""
|
| 607 |
|
| 608 |
-
|
| 609 |
<script>
|
| 610 |
function setCookie(name, value, days) {
|
| 611 |
var expires = "";
|
|
@@ -631,6 +645,7 @@ function getCookie(name) {
|
|
| 631 |
(function() {
|
| 632 |
const urlParams = new URLSearchParams(window.location.search);
|
| 633 |
const writeToken = urlParams.get('write_token');
|
|
|
|
| 634 |
|
| 635 |
if (writeToken) {
|
| 636 |
setCookie('trackio_write_token', writeToken, 7);
|
|
@@ -646,6 +661,12 @@ function getCookie(name) {
|
|
| 646 |
window.history.replaceState({}, document.title, newUrl);
|
| 647 |
}
|
| 648 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
})();
|
| 650 |
</script>
|
| 651 |
"""
|
|
@@ -653,7 +674,7 @@ function getCookie(name) {
|
|
| 653 |
|
| 654 |
gr.set_static_paths(paths=[utils.MEDIA_DIR])
|
| 655 |
|
| 656 |
-
with gr.Blocks(title="Trackio Dashboard"
|
| 657 |
with gr.Sidebar(open=False) as sidebar:
|
| 658 |
logo_urls = utils.get_logo_urls()
|
| 659 |
logo = gr.Markdown(
|
|
@@ -698,7 +719,8 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 698 |
choices=["step", "time"],
|
| 699 |
value="step",
|
| 700 |
)
|
| 701 |
-
|
|
|
|
| 702 |
metric_filter_tb = gr.Textbox(
|
| 703 |
label="Metric Filter (regex)",
|
| 704 |
placeholder="e.g., loss|ndcg@10|gpu",
|
|
@@ -726,7 +748,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 726 |
smoothing_slider,
|
| 727 |
],
|
| 728 |
queue=False,
|
| 729 |
-
|
| 730 |
)
|
| 731 |
gr.on(
|
| 732 |
[demo.load],
|
|
@@ -734,7 +756,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 734 |
outputs=project_dd,
|
| 735 |
show_progress="hidden",
|
| 736 |
queue=False,
|
| 737 |
-
|
| 738 |
)
|
| 739 |
gr.on(
|
| 740 |
[timer.tick],
|
|
@@ -742,14 +764,14 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 742 |
inputs=[project_dd, run_tb, run_selection_state, selected_runs_from_url],
|
| 743 |
outputs=[run_cb, run_tb, run_selection_state],
|
| 744 |
show_progress="hidden",
|
| 745 |
-
|
| 746 |
)
|
| 747 |
gr.on(
|
| 748 |
[timer.tick],
|
| 749 |
fn=lambda: gr.Dropdown(info=fns.get_project_info()),
|
| 750 |
outputs=[project_dd],
|
| 751 |
show_progress="hidden",
|
| 752 |
-
|
| 753 |
)
|
| 754 |
gr.on(
|
| 755 |
[demo.load, project_dd.change],
|
|
@@ -758,34 +780,34 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 758 |
outputs=[run_cb, run_tb, run_selection_state],
|
| 759 |
show_progress="hidden",
|
| 760 |
queue=False,
|
| 761 |
-
|
| 762 |
).then(
|
| 763 |
fn=update_x_axis_choices,
|
| 764 |
inputs=[project_dd, run_selection_state],
|
| 765 |
outputs=x_axis_dd,
|
| 766 |
show_progress="hidden",
|
| 767 |
queue=False,
|
| 768 |
-
|
| 769 |
).then(
|
| 770 |
fn=generate_embed,
|
| 771 |
inputs=[project_dd, metric_filter_tb, run_selection_state],
|
| 772 |
outputs=[embed_code],
|
| 773 |
show_progress="hidden",
|
| 774 |
-
|
| 775 |
queue=False,
|
| 776 |
).then(
|
| 777 |
fns.update_navbar_value,
|
| 778 |
inputs=[project_dd],
|
| 779 |
outputs=[navbar],
|
| 780 |
show_progress="hidden",
|
| 781 |
-
|
| 782 |
queue=False,
|
| 783 |
).then(
|
| 784 |
fn=fns.get_group_by_fields,
|
| 785 |
inputs=[project_dd],
|
| 786 |
outputs=[run_group_by_dd],
|
| 787 |
show_progress="hidden",
|
| 788 |
-
|
| 789 |
queue=False,
|
| 790 |
)
|
| 791 |
|
|
@@ -796,7 +818,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 796 |
outputs=x_axis_dd,
|
| 797 |
show_progress="hidden",
|
| 798 |
queue=False,
|
| 799 |
-
|
| 800 |
)
|
| 801 |
gr.on(
|
| 802 |
[metric_filter_tb.change, run_cb.change],
|
|
@@ -804,7 +826,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 804 |
inputs=[project_dd, metric_filter_tb, run_selection_state],
|
| 805 |
outputs=embed_code,
|
| 806 |
show_progress="hidden",
|
| 807 |
-
|
| 808 |
queue=False,
|
| 809 |
)
|
| 810 |
|
|
@@ -820,7 +842,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 820 |
inputs=[run_group_by_dd],
|
| 821 |
outputs=[run_cb, grouped_runs_panel],
|
| 822 |
show_progress="hidden",
|
| 823 |
-
|
| 824 |
queue=False,
|
| 825 |
)
|
| 826 |
|
|
@@ -828,28 +850,28 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 828 |
fn=toggle_timer,
|
| 829 |
inputs=realtime_cb,
|
| 830 |
outputs=timer,
|
| 831 |
-
|
| 832 |
queue=False,
|
| 833 |
)
|
| 834 |
run_cb.input(
|
| 835 |
fn=fns.handle_run_checkbox_change,
|
| 836 |
inputs=[run_cb, run_selection_state],
|
| 837 |
outputs=run_selection_state,
|
| 838 |
-
|
| 839 |
queue=False,
|
| 840 |
).then(
|
| 841 |
fn=generate_embed,
|
| 842 |
inputs=[project_dd, metric_filter_tb, run_selection_state],
|
| 843 |
outputs=embed_code,
|
| 844 |
show_progress="hidden",
|
| 845 |
-
|
| 846 |
queue=False,
|
| 847 |
)
|
| 848 |
run_tb.input(
|
| 849 |
fn=refresh_runs,
|
| 850 |
inputs=[project_dd, run_tb, run_selection_state],
|
| 851 |
outputs=[run_cb, run_tb, run_selection_state],
|
| 852 |
-
|
| 853 |
queue=False,
|
| 854 |
show_progress="hidden",
|
| 855 |
)
|
|
@@ -911,7 +933,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 911 |
inputs=[project_dd],
|
| 912 |
outputs=last_steps,
|
| 913 |
show_progress="hidden",
|
| 914 |
-
|
| 915 |
)
|
| 916 |
|
| 917 |
@gr.render(
|
|
@@ -922,7 +944,8 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 922 |
smoothing_slider.change,
|
| 923 |
x_lim.change,
|
| 924 |
x_axis_dd.change,
|
| 925 |
-
|
|
|
|
| 926 |
metric_filter_tb.change,
|
| 927 |
],
|
| 928 |
inputs=[
|
|
@@ -932,7 +955,8 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 932 |
metrics_subset,
|
| 933 |
x_lim,
|
| 934 |
x_axis_dd,
|
| 935 |
-
|
|
|
|
| 936 |
metric_filter_tb,
|
| 937 |
],
|
| 938 |
show_progress="hidden",
|
|
@@ -945,7 +969,8 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 945 |
metrics_subset,
|
| 946 |
x_lim_value,
|
| 947 |
x_axis,
|
| 948 |
-
|
|
|
|
| 949 |
metric_filter,
|
| 950 |
):
|
| 951 |
dfs = []
|
|
@@ -954,7 +979,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 954 |
|
| 955 |
for run in runs:
|
| 956 |
df, media_by_key = load_run_data(
|
| 957 |
-
project, run, smoothing_granularity, x_axis,
|
| 958 |
)
|
| 959 |
if df is not None:
|
| 960 |
dfs.append(df)
|
|
@@ -1058,13 +1083,13 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 1058 |
y_title=metric_name.split("/")[-1],
|
| 1059 |
color=color,
|
| 1060 |
color_map=color_map,
|
|
|
|
| 1061 |
title=metric_name,
|
| 1062 |
key=f"plot-{metric_idx}",
|
| 1063 |
preserved_by_key=None,
|
|
|
|
| 1064 |
x_lim=updated_x_lim,
|
| 1065 |
-
show_fullscreen_button=True,
|
| 1066 |
min_width=400,
|
| 1067 |
-
show_export_button=True,
|
| 1068 |
)
|
| 1069 |
plot.select(
|
| 1070 |
update_x_lim,
|
|
@@ -1123,13 +1148,13 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 1123 |
y_title=metric_name.split("/")[-1],
|
| 1124 |
color=color,
|
| 1125 |
color_map=color_map,
|
|
|
|
| 1126 |
title=metric_name,
|
| 1127 |
key=f"plot-{metric_idx}",
|
| 1128 |
preserved_by_key=None,
|
|
|
|
| 1129 |
x_lim=updated_x_lim,
|
| 1130 |
-
show_fullscreen_button=True,
|
| 1131 |
min_width=400,
|
| 1132 |
-
show_export_button=True,
|
| 1133 |
)
|
| 1134 |
plot.select(
|
| 1135 |
update_x_lim,
|
|
@@ -1145,59 +1170,113 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 1145 |
if media_by_run and any(any(media) for media in media_by_run.values()):
|
| 1146 |
create_media_section(media_by_run)
|
| 1147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1148 |
table_cols = master_df.select_dtypes(include="object").columns
|
| 1149 |
table_cols = [c for c in table_cols if c not in utils.RESERVED_KEYS]
|
| 1150 |
-
if
|
| 1151 |
-
table_cols = [c for c in table_cols if c in
|
| 1152 |
if metric_filter and metric_filter.strip():
|
| 1153 |
table_cols = filter_metrics_by_regex(list(table_cols), metric_filter)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1154 |
|
| 1155 |
-
|
| 1156 |
-
|
| 1157 |
-
for metric_name in table_cols
|
| 1158 |
-
if not (metric_df := master_df.dropna(subset=[metric_name])).empty
|
| 1159 |
-
and isinstance(value := metric_df[metric_name].iloc[-1], dict)
|
| 1160 |
-
and value.get("_type") == Table.TYPE
|
| 1161 |
-
)
|
| 1162 |
-
|
| 1163 |
-
if actual_table_count > 0:
|
| 1164 |
-
with gr.Accordion(f"tables ({actual_table_count})", open=True):
|
| 1165 |
with gr.Row(key="row"):
|
| 1166 |
for metric_idx, metric_name in enumerate(table_cols):
|
| 1167 |
metric_df = master_df.dropna(subset=[metric_name])
|
| 1168 |
if not metric_df.empty:
|
| 1169 |
-
value = metric_df[metric_name]
|
|
|
|
| 1170 |
if (
|
| 1171 |
-
isinstance(
|
| 1172 |
-
and "_type" in
|
| 1173 |
-
and
|
| 1174 |
):
|
| 1175 |
try:
|
| 1176 |
-
|
| 1177 |
-
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1183 |
except Exception as e:
|
| 1184 |
gr.Warning(
|
| 1185 |
f"Column {metric_name} failed to render as a table: {e}"
|
| 1186 |
)
|
| 1187 |
|
| 1188 |
-
# Display histograms
|
| 1189 |
histogram_cols = set(master_df.columns) - {
|
| 1190 |
"run",
|
| 1191 |
"step",
|
| 1192 |
"timestamp",
|
| 1193 |
"data_type",
|
| 1194 |
}
|
| 1195 |
-
if metrics_subset:
|
| 1196 |
-
histogram_cols = [c for c in histogram_cols if c in metrics_subset]
|
| 1197 |
-
if metric_filter and metric_filter.strip():
|
| 1198 |
-
histogram_cols = filter_metrics_by_regex(
|
| 1199 |
-
list(histogram_cols), metric_filter
|
| 1200 |
-
)
|
| 1201 |
|
| 1202 |
actual_histogram_count = sum(
|
| 1203 |
1
|
|
@@ -1338,7 +1417,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 1338 |
run_cb,
|
| 1339 |
],
|
| 1340 |
show_progress="hidden",
|
| 1341 |
-
|
| 1342 |
queue=False,
|
| 1343 |
)
|
| 1344 |
|
|
@@ -1352,7 +1431,7 @@ with gr.Blocks(title="Trackio Dashboard", css=css, head=javascript) as demo:
|
|
| 1352 |
],
|
| 1353 |
outputs=[run_selection_state, group_cb, run_cb],
|
| 1354 |
show_progress="hidden",
|
| 1355 |
-
|
| 1356 |
queue=False,
|
| 1357 |
)
|
| 1358 |
|
|
|
|
| 99 |
return SQLiteStorage.get_runs(project)
|
| 100 |
|
| 101 |
|
| 102 |
+
def upload_db_to_space(
|
| 103 |
+
project: str, uploaded_db: gr.FileData, hf_token: str | None
|
| 104 |
+
) -> None:
|
| 105 |
+
"""
|
| 106 |
+
Uploads the database of a local Trackio project to a Hugging Face Space.
|
| 107 |
+
"""
|
| 108 |
+
fns.check_hf_token_has_write_access(hf_token)
|
| 109 |
+
db_project_path = SQLiteStorage.get_project_db_path(project)
|
| 110 |
+
os.makedirs(os.path.dirname(db_project_path), exist_ok=True)
|
| 111 |
+
shutil.copy(uploaded_db["path"], db_project_path)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
def get_available_metrics(project: str, runs: list[str]) -> list[str]:
|
| 115 |
"""Get all available metrics across all runs for x-axis selection."""
|
| 116 |
if not project or not runs:
|
|
|
|
| 174 |
def load_run_data(
|
| 175 |
project: str | None,
|
| 176 |
run: str | None,
|
| 177 |
+
smoothing_granularity: int = 0,
|
| 178 |
+
x_axis: str = "step",
|
| 179 |
+
log_scale_x: bool = False,
|
| 180 |
+
log_scale_y: bool = False,
|
| 181 |
) -> tuple[pd.DataFrame, dict]:
|
| 182 |
if not project or not run:
|
| 183 |
return None, None
|
|
|
|
| 202 |
else:
|
| 203 |
x_column = x_axis
|
| 204 |
|
| 205 |
+
if log_scale_x and x_column in df.columns:
|
| 206 |
x_vals = df[x_column]
|
| 207 |
if (x_vals <= 0).any():
|
| 208 |
df[x_column] = np.log10(np.maximum(x_vals, 0) + 1)
|
| 209 |
else:
|
| 210 |
df[x_column] = np.log10(x_vals)
|
| 211 |
|
| 212 |
+
if log_scale_y:
|
| 213 |
+
numeric_cols = df.select_dtypes(include="number").columns
|
| 214 |
+
y_cols = [
|
| 215 |
+
c for c in numeric_cols if c not in utils.RESERVED_KEYS and c != x_column
|
| 216 |
+
]
|
| 217 |
+
for y_col in y_cols:
|
| 218 |
+
if y_col in df.columns:
|
| 219 |
+
y_vals = df[y_col]
|
| 220 |
+
if (y_vals <= 0).any():
|
| 221 |
+
df[y_col] = np.log10(np.maximum(y_vals, 0) + 1)
|
| 222 |
+
else:
|
| 223 |
+
df[y_col] = np.log10(y_vals)
|
| 224 |
+
|
| 225 |
if smoothing_granularity > 0:
|
| 226 |
numeric_cols = df.select_dtypes(include="number").columns
|
| 227 |
numeric_cols = [c for c in numeric_cols if c not in utils.RESERVED_KEYS]
|
|
|
|
| 297 |
return gr.Timer(active=False)
|
| 298 |
|
| 299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
def bulk_upload_media(uploads: list[UploadEntry], hf_token: str | None) -> None:
|
| 301 |
"""
|
| 302 |
Uploads media files to a Trackio dashboard. Each entry in the list is a tuple of the project, run, and media file to be uploaded.
|
|
|
|
| 558 |
)
|
| 559 |
|
| 560 |
|
| 561 |
+
CSS = """
|
| 562 |
#run-cb .wrap { gap: 2px; }
|
| 563 |
#run-cb .wrap label {
|
| 564 |
line-height: 1;
|
|
|
|
| 613 |
gap: 0.25em;
|
| 614 |
margin-bottom: 0.25em;
|
| 615 |
}
|
| 616 |
+
|
| 617 |
+
.tab-like-container {
|
| 618 |
+
visibility: hidden;
|
| 619 |
+
}
|
| 620 |
"""
|
| 621 |
|
| 622 |
+
HEAD = """
|
| 623 |
<script>
|
| 624 |
function setCookie(name, value, days) {
|
| 625 |
var expires = "";
|
|
|
|
| 645 |
(function() {
|
| 646 |
const urlParams = new URLSearchParams(window.location.search);
|
| 647 |
const writeToken = urlParams.get('write_token');
|
| 648 |
+
const footerParam = urlParams.get('footer');
|
| 649 |
|
| 650 |
if (writeToken) {
|
| 651 |
setCookie('trackio_write_token', writeToken, 7);
|
|
|
|
| 661 |
window.history.replaceState({}, document.title, newUrl);
|
| 662 |
}
|
| 663 |
}
|
| 664 |
+
|
| 665 |
+
if (footerParam === 'false') {
|
| 666 |
+
const style = document.createElement('style');
|
| 667 |
+
style.textContent = 'footer { display: none !important; }';
|
| 668 |
+
document.head.appendChild(style);
|
| 669 |
+
}
|
| 670 |
})();
|
| 671 |
</script>
|
| 672 |
"""
|
|
|
|
| 674 |
|
| 675 |
gr.set_static_paths(paths=[utils.MEDIA_DIR])
|
| 676 |
|
| 677 |
+
with gr.Blocks(title="Trackio Dashboard") as demo:
|
| 678 |
with gr.Sidebar(open=False) as sidebar:
|
| 679 |
logo_urls = utils.get_logo_urls()
|
| 680 |
logo = gr.Markdown(
|
|
|
|
| 719 |
choices=["step", "time"],
|
| 720 |
value="step",
|
| 721 |
)
|
| 722 |
+
log_scale_x_cb = gr.Checkbox(label="Log scale X-axis", value=False)
|
| 723 |
+
log_scale_y_cb = gr.Checkbox(label="Log scale Y-axis", value=False)
|
| 724 |
metric_filter_tb = gr.Textbox(
|
| 725 |
label="Metric Filter (regex)",
|
| 726 |
placeholder="e.g., loss|ndcg@10|gpu",
|
|
|
|
| 748 |
smoothing_slider,
|
| 749 |
],
|
| 750 |
queue=False,
|
| 751 |
+
api_visibility="private",
|
| 752 |
)
|
| 753 |
gr.on(
|
| 754 |
[demo.load],
|
|
|
|
| 756 |
outputs=project_dd,
|
| 757 |
show_progress="hidden",
|
| 758 |
queue=False,
|
| 759 |
+
api_visibility="private",
|
| 760 |
)
|
| 761 |
gr.on(
|
| 762 |
[timer.tick],
|
|
|
|
| 764 |
inputs=[project_dd, run_tb, run_selection_state, selected_runs_from_url],
|
| 765 |
outputs=[run_cb, run_tb, run_selection_state],
|
| 766 |
show_progress="hidden",
|
| 767 |
+
api_visibility="private",
|
| 768 |
)
|
| 769 |
gr.on(
|
| 770 |
[timer.tick],
|
| 771 |
fn=lambda: gr.Dropdown(info=fns.get_project_info()),
|
| 772 |
outputs=[project_dd],
|
| 773 |
show_progress="hidden",
|
| 774 |
+
api_visibility="private",
|
| 775 |
)
|
| 776 |
gr.on(
|
| 777 |
[demo.load, project_dd.change],
|
|
|
|
| 780 |
outputs=[run_cb, run_tb, run_selection_state],
|
| 781 |
show_progress="hidden",
|
| 782 |
queue=False,
|
| 783 |
+
api_visibility="private",
|
| 784 |
).then(
|
| 785 |
fn=update_x_axis_choices,
|
| 786 |
inputs=[project_dd, run_selection_state],
|
| 787 |
outputs=x_axis_dd,
|
| 788 |
show_progress="hidden",
|
| 789 |
queue=False,
|
| 790 |
+
api_visibility="private",
|
| 791 |
).then(
|
| 792 |
fn=generate_embed,
|
| 793 |
inputs=[project_dd, metric_filter_tb, run_selection_state],
|
| 794 |
outputs=[embed_code],
|
| 795 |
show_progress="hidden",
|
| 796 |
+
api_visibility="private",
|
| 797 |
queue=False,
|
| 798 |
).then(
|
| 799 |
fns.update_navbar_value,
|
| 800 |
inputs=[project_dd],
|
| 801 |
outputs=[navbar],
|
| 802 |
show_progress="hidden",
|
| 803 |
+
api_visibility="private",
|
| 804 |
queue=False,
|
| 805 |
).then(
|
| 806 |
fn=fns.get_group_by_fields,
|
| 807 |
inputs=[project_dd],
|
| 808 |
outputs=[run_group_by_dd],
|
| 809 |
show_progress="hidden",
|
| 810 |
+
api_visibility="private",
|
| 811 |
queue=False,
|
| 812 |
)
|
| 813 |
|
|
|
|
| 818 |
outputs=x_axis_dd,
|
| 819 |
show_progress="hidden",
|
| 820 |
queue=False,
|
| 821 |
+
api_visibility="private",
|
| 822 |
)
|
| 823 |
gr.on(
|
| 824 |
[metric_filter_tb.change, run_cb.change],
|
|
|
|
| 826 |
inputs=[project_dd, metric_filter_tb, run_selection_state],
|
| 827 |
outputs=embed_code,
|
| 828 |
show_progress="hidden",
|
| 829 |
+
api_visibility="private",
|
| 830 |
queue=False,
|
| 831 |
)
|
| 832 |
|
|
|
|
| 842 |
inputs=[run_group_by_dd],
|
| 843 |
outputs=[run_cb, grouped_runs_panel],
|
| 844 |
show_progress="hidden",
|
| 845 |
+
api_visibility="private",
|
| 846 |
queue=False,
|
| 847 |
)
|
| 848 |
|
|
|
|
| 850 |
fn=toggle_timer,
|
| 851 |
inputs=realtime_cb,
|
| 852 |
outputs=timer,
|
| 853 |
+
api_visibility="private",
|
| 854 |
queue=False,
|
| 855 |
)
|
| 856 |
run_cb.input(
|
| 857 |
fn=fns.handle_run_checkbox_change,
|
| 858 |
inputs=[run_cb, run_selection_state],
|
| 859 |
outputs=run_selection_state,
|
| 860 |
+
api_visibility="private",
|
| 861 |
queue=False,
|
| 862 |
).then(
|
| 863 |
fn=generate_embed,
|
| 864 |
inputs=[project_dd, metric_filter_tb, run_selection_state],
|
| 865 |
outputs=embed_code,
|
| 866 |
show_progress="hidden",
|
| 867 |
+
api_visibility="private",
|
| 868 |
queue=False,
|
| 869 |
)
|
| 870 |
run_tb.input(
|
| 871 |
fn=refresh_runs,
|
| 872 |
inputs=[project_dd, run_tb, run_selection_state],
|
| 873 |
outputs=[run_cb, run_tb, run_selection_state],
|
| 874 |
+
api_visibility="private",
|
| 875 |
queue=False,
|
| 876 |
show_progress="hidden",
|
| 877 |
)
|
|
|
|
| 933 |
inputs=[project_dd],
|
| 934 |
outputs=last_steps,
|
| 935 |
show_progress="hidden",
|
| 936 |
+
api_visibility="private",
|
| 937 |
)
|
| 938 |
|
| 939 |
@gr.render(
|
|
|
|
| 944 |
smoothing_slider.change,
|
| 945 |
x_lim.change,
|
| 946 |
x_axis_dd.change,
|
| 947 |
+
log_scale_x_cb.change,
|
| 948 |
+
log_scale_y_cb.change,
|
| 949 |
metric_filter_tb.change,
|
| 950 |
],
|
| 951 |
inputs=[
|
|
|
|
| 955 |
metrics_subset,
|
| 956 |
x_lim,
|
| 957 |
x_axis_dd,
|
| 958 |
+
log_scale_x_cb,
|
| 959 |
+
log_scale_y_cb,
|
| 960 |
metric_filter_tb,
|
| 961 |
],
|
| 962 |
show_progress="hidden",
|
|
|
|
| 969 |
metrics_subset,
|
| 970 |
x_lim_value,
|
| 971 |
x_axis,
|
| 972 |
+
log_scale_x,
|
| 973 |
+
log_scale_y,
|
| 974 |
metric_filter,
|
| 975 |
):
|
| 976 |
dfs = []
|
|
|
|
| 979 |
|
| 980 |
for run in runs:
|
| 981 |
df, media_by_key = load_run_data(
|
| 982 |
+
project, run, smoothing_granularity, x_axis, log_scale_x, log_scale_y
|
| 983 |
)
|
| 984 |
if df is not None:
|
| 985 |
dfs.append(df)
|
|
|
|
| 1083 |
y_title=metric_name.split("/")[-1],
|
| 1084 |
color=color,
|
| 1085 |
color_map=color_map,
|
| 1086 |
+
colors_in_legend=original_runs,
|
| 1087 |
title=metric_name,
|
| 1088 |
key=f"plot-{metric_idx}",
|
| 1089 |
preserved_by_key=None,
|
| 1090 |
+
buttons=["fullscreen", "export"],
|
| 1091 |
x_lim=updated_x_lim,
|
|
|
|
| 1092 |
min_width=400,
|
|
|
|
| 1093 |
)
|
| 1094 |
plot.select(
|
| 1095 |
update_x_lim,
|
|
|
|
| 1148 |
y_title=metric_name.split("/")[-1],
|
| 1149 |
color=color,
|
| 1150 |
color_map=color_map,
|
| 1151 |
+
colors_in_legend=original_runs,
|
| 1152 |
title=metric_name,
|
| 1153 |
key=f"plot-{metric_idx}",
|
| 1154 |
preserved_by_key=None,
|
| 1155 |
+
buttons=["fullscreen", "export"],
|
| 1156 |
x_lim=updated_x_lim,
|
|
|
|
| 1157 |
min_width=400,
|
|
|
|
| 1158 |
)
|
| 1159 |
plot.select(
|
| 1160 |
update_x_lim,
|
|
|
|
| 1170 |
if media_by_run and any(any(media) for media in media_by_run.values()):
|
| 1171 |
create_media_section(media_by_run)
|
| 1172 |
|
| 1173 |
+
@gr.render(
|
| 1174 |
+
triggers=[
|
| 1175 |
+
demo.load,
|
| 1176 |
+
run_cb.change,
|
| 1177 |
+
last_steps.change,
|
| 1178 |
+
metric_filter_tb.change,
|
| 1179 |
+
],
|
| 1180 |
+
inputs=[
|
| 1181 |
+
project_dd,
|
| 1182 |
+
run_cb,
|
| 1183 |
+
metrics_subset,
|
| 1184 |
+
metric_filter_tb,
|
| 1185 |
+
],
|
| 1186 |
+
show_progress="hidden",
|
| 1187 |
+
queue=False,
|
| 1188 |
+
)
|
| 1189 |
+
def update_tables(
|
| 1190 |
+
project,
|
| 1191 |
+
runs,
|
| 1192 |
+
metrics_subset_value,
|
| 1193 |
+
metric_filter,
|
| 1194 |
+
):
|
| 1195 |
+
dfs = []
|
| 1196 |
+
for run in runs:
|
| 1197 |
+
df, _ = load_run_data(project, run)
|
| 1198 |
+
if df is not None:
|
| 1199 |
+
dfs.append(df)
|
| 1200 |
+
master_df = pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
|
| 1201 |
+
|
| 1202 |
table_cols = master_df.select_dtypes(include="object").columns
|
| 1203 |
table_cols = [c for c in table_cols if c not in utils.RESERVED_KEYS]
|
| 1204 |
+
if metrics_subset_value:
|
| 1205 |
+
table_cols = [c for c in table_cols if c in metrics_subset_value]
|
| 1206 |
if metric_filter and metric_filter.strip():
|
| 1207 |
table_cols = filter_metrics_by_regex(list(table_cols), metric_filter)
|
| 1208 |
+
table_cols = [
|
| 1209 |
+
c
|
| 1210 |
+
for c in table_cols
|
| 1211 |
+
if not (metric_df := master_df.dropna(subset=[c])).empty
|
| 1212 |
+
and isinstance(first_value := metric_df[c].iloc[0], dict)
|
| 1213 |
+
and first_value.get("_type") == Table.TYPE
|
| 1214 |
+
]
|
| 1215 |
|
| 1216 |
+
if len(table_cols) > 0:
|
| 1217 |
+
with gr.Accordion(f"tables ({len(table_cols)})", open=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1218 |
with gr.Row(key="row"):
|
| 1219 |
for metric_idx, metric_name in enumerate(table_cols):
|
| 1220 |
metric_df = master_df.dropna(subset=[metric_name])
|
| 1221 |
if not metric_df.empty:
|
| 1222 |
+
value = metric_df[metric_name]
|
| 1223 |
+
first_value = value.iloc[0]
|
| 1224 |
if (
|
| 1225 |
+
isinstance(first_value, dict)
|
| 1226 |
+
and "_type" in first_value
|
| 1227 |
+
and first_value["_type"] == Table.TYPE
|
| 1228 |
):
|
| 1229 |
try:
|
| 1230 |
+
with gr.Column():
|
| 1231 |
+
s = gr.Slider(
|
| 1232 |
+
value=len(value),
|
| 1233 |
+
minimum=1,
|
| 1234 |
+
maximum=len(value),
|
| 1235 |
+
step=1,
|
| 1236 |
+
container=False,
|
| 1237 |
+
visible=len(value) > 1,
|
| 1238 |
+
)
|
| 1239 |
+
processed_data = Table.to_display_format(
|
| 1240 |
+
value.iloc[-1]["_value"]
|
| 1241 |
+
)
|
| 1242 |
+
df = pd.DataFrame(processed_data)
|
| 1243 |
+
table = gr.DataFrame(
|
| 1244 |
+
df,
|
| 1245 |
+
label=f"{metric_name} (index {len(value)})",
|
| 1246 |
+
key=f"table-{metric_idx}",
|
| 1247 |
+
wrap=True,
|
| 1248 |
+
datatype="markdown",
|
| 1249 |
+
preserved_by_key=None,
|
| 1250 |
+
)
|
| 1251 |
+
|
| 1252 |
+
def get_table_at_index(index: int):
|
| 1253 |
+
value = metric_df[metric_name]
|
| 1254 |
+
processed_data = Table.to_display_format(
|
| 1255 |
+
value.iloc[index - 1]["_value"]
|
| 1256 |
+
)
|
| 1257 |
+
df_ = pd.DataFrame(processed_data)
|
| 1258 |
+
return gr.Dataframe(
|
| 1259 |
+
df_,
|
| 1260 |
+
label=f"{metric_name} (index {index})",
|
| 1261 |
+
)
|
| 1262 |
+
|
| 1263 |
+
s.input(
|
| 1264 |
+
get_table_at_index,
|
| 1265 |
+
inputs=s,
|
| 1266 |
+
outputs=table,
|
| 1267 |
+
show_progress="hidden",
|
| 1268 |
+
)
|
| 1269 |
except Exception as e:
|
| 1270 |
gr.Warning(
|
| 1271 |
f"Column {metric_name} failed to render as a table: {e}"
|
| 1272 |
)
|
| 1273 |
|
|
|
|
| 1274 |
histogram_cols = set(master_df.columns) - {
|
| 1275 |
"run",
|
| 1276 |
"step",
|
| 1277 |
"timestamp",
|
| 1278 |
"data_type",
|
| 1279 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1280 |
|
| 1281 |
actual_histogram_count = sum(
|
| 1282 |
1
|
|
|
|
| 1417 |
run_cb,
|
| 1418 |
],
|
| 1419 |
show_progress="hidden",
|
| 1420 |
+
api_visibility="private",
|
| 1421 |
queue=False,
|
| 1422 |
)
|
| 1423 |
|
|
|
|
| 1431 |
],
|
| 1432 |
outputs=[run_selection_state, group_cb, run_cb],
|
| 1433 |
show_progress="hidden",
|
| 1434 |
+
api_visibility="private",
|
| 1435 |
queue=False,
|
| 1436 |
)
|
| 1437 |
|
ui/run_detail.py
CHANGED
|
@@ -73,13 +73,13 @@ with gr.Blocks() as run_detail_page:
|
|
| 73 |
outputs=[project_dd, run_dd],
|
| 74 |
show_progress="hidden",
|
| 75 |
queue=False,
|
| 76 |
-
|
| 77 |
).then(
|
| 78 |
fns.update_navbar_value,
|
| 79 |
inputs=[project_dd],
|
| 80 |
outputs=[navbar],
|
| 81 |
show_progress="hidden",
|
| 82 |
-
|
| 83 |
queue=False,
|
| 84 |
)
|
| 85 |
|
|
@@ -89,6 +89,6 @@ with gr.Blocks() as run_detail_page:
|
|
| 89 |
inputs=[project_dd, run_dd],
|
| 90 |
outputs=[run_details, run_config],
|
| 91 |
show_progress="hidden",
|
| 92 |
-
|
| 93 |
queue=False,
|
| 94 |
)
|
|
|
|
| 73 |
outputs=[project_dd, run_dd],
|
| 74 |
show_progress="hidden",
|
| 75 |
queue=False,
|
| 76 |
+
api_visibility="private",
|
| 77 |
).then(
|
| 78 |
fns.update_navbar_value,
|
| 79 |
inputs=[project_dd],
|
| 80 |
outputs=[navbar],
|
| 81 |
show_progress="hidden",
|
| 82 |
+
api_visibility="private",
|
| 83 |
queue=False,
|
| 84 |
)
|
| 85 |
|
|
|
|
| 89 |
inputs=[project_dd, run_dd],
|
| 90 |
outputs=[run_details, run_config],
|
| 91 |
show_progress="hidden",
|
| 92 |
+
api_visibility="private",
|
| 93 |
queue=False,
|
| 94 |
)
|
ui/runs.py
CHANGED
|
@@ -200,14 +200,14 @@ with gr.Blocks() as run_page:
|
|
| 200 |
outputs=project_dd,
|
| 201 |
show_progress="hidden",
|
| 202 |
queue=False,
|
| 203 |
-
|
| 204 |
)
|
| 205 |
gr.on(
|
| 206 |
[timer.tick],
|
| 207 |
fn=lambda: gr.Dropdown(info=fns.get_project_info()),
|
| 208 |
outputs=[project_dd],
|
| 209 |
show_progress="hidden",
|
| 210 |
-
|
| 211 |
)
|
| 212 |
gr.on(
|
| 213 |
[project_dd.change],
|
|
@@ -215,14 +215,14 @@ with gr.Blocks() as run_page:
|
|
| 215 |
inputs=[project_dd],
|
| 216 |
outputs=[runs_table],
|
| 217 |
show_progress="hidden",
|
| 218 |
-
|
| 219 |
queue=False,
|
| 220 |
).then(
|
| 221 |
fns.update_navbar_value,
|
| 222 |
inputs=[project_dd],
|
| 223 |
outputs=[navbar],
|
| 224 |
show_progress="hidden",
|
| 225 |
-
|
| 226 |
queue=False,
|
| 227 |
)
|
| 228 |
|
|
@@ -232,7 +232,7 @@ with gr.Blocks() as run_page:
|
|
| 232 |
inputs=[],
|
| 233 |
outputs=[delete_run_btn, runs_table, allow_deleting_runs],
|
| 234 |
show_progress="hidden",
|
| 235 |
-
|
| 236 |
queue=False,
|
| 237 |
)
|
| 238 |
gr.on(
|
|
@@ -241,7 +241,7 @@ with gr.Blocks() as run_page:
|
|
| 241 |
inputs=[allow_deleting_runs, runs_table],
|
| 242 |
outputs=[delete_run_btn],
|
| 243 |
show_progress="hidden",
|
| 244 |
-
|
| 245 |
queue=False,
|
| 246 |
)
|
| 247 |
gr.on(
|
|
@@ -254,7 +254,7 @@ with gr.Blocks() as run_page:
|
|
| 254 |
inputs=None,
|
| 255 |
outputs=[delete_run_btn, confirm_btn, cancel_btn],
|
| 256 |
show_progress="hidden",
|
| 257 |
-
|
| 258 |
queue=False,
|
| 259 |
)
|
| 260 |
gr.on(
|
|
@@ -267,7 +267,7 @@ with gr.Blocks() as run_page:
|
|
| 267 |
inputs=None,
|
| 268 |
outputs=[delete_run_btn, confirm_btn, cancel_btn],
|
| 269 |
show_progress="hidden",
|
| 270 |
-
|
| 271 |
queue=False,
|
| 272 |
)
|
| 273 |
gr.on(
|
|
@@ -276,6 +276,6 @@ with gr.Blocks() as run_page:
|
|
| 276 |
inputs=[allow_deleting_runs, runs_table, project_dd],
|
| 277 |
outputs=[runs_table],
|
| 278 |
show_progress="hidden",
|
| 279 |
-
|
| 280 |
queue=False,
|
| 281 |
)
|
|
|
|
| 200 |
outputs=project_dd,
|
| 201 |
show_progress="hidden",
|
| 202 |
queue=False,
|
| 203 |
+
api_visibility="private",
|
| 204 |
)
|
| 205 |
gr.on(
|
| 206 |
[timer.tick],
|
| 207 |
fn=lambda: gr.Dropdown(info=fns.get_project_info()),
|
| 208 |
outputs=[project_dd],
|
| 209 |
show_progress="hidden",
|
| 210 |
+
api_visibility="private",
|
| 211 |
)
|
| 212 |
gr.on(
|
| 213 |
[project_dd.change],
|
|
|
|
| 215 |
inputs=[project_dd],
|
| 216 |
outputs=[runs_table],
|
| 217 |
show_progress="hidden",
|
| 218 |
+
api_visibility="private",
|
| 219 |
queue=False,
|
| 220 |
).then(
|
| 221 |
fns.update_navbar_value,
|
| 222 |
inputs=[project_dd],
|
| 223 |
outputs=[navbar],
|
| 224 |
show_progress="hidden",
|
| 225 |
+
api_visibility="private",
|
| 226 |
queue=False,
|
| 227 |
)
|
| 228 |
|
|
|
|
| 232 |
inputs=[],
|
| 233 |
outputs=[delete_run_btn, runs_table, allow_deleting_runs],
|
| 234 |
show_progress="hidden",
|
| 235 |
+
api_visibility="private",
|
| 236 |
queue=False,
|
| 237 |
)
|
| 238 |
gr.on(
|
|
|
|
| 241 |
inputs=[allow_deleting_runs, runs_table],
|
| 242 |
outputs=[delete_run_btn],
|
| 243 |
show_progress="hidden",
|
| 244 |
+
api_visibility="private",
|
| 245 |
queue=False,
|
| 246 |
)
|
| 247 |
gr.on(
|
|
|
|
| 254 |
inputs=None,
|
| 255 |
outputs=[delete_run_btn, confirm_btn, cancel_btn],
|
| 256 |
show_progress="hidden",
|
| 257 |
+
api_visibility="private",
|
| 258 |
queue=False,
|
| 259 |
)
|
| 260 |
gr.on(
|
|
|
|
| 267 |
inputs=None,
|
| 268 |
outputs=[delete_run_btn, confirm_btn, cancel_btn],
|
| 269 |
show_progress="hidden",
|
| 270 |
+
api_visibility="private",
|
| 271 |
queue=False,
|
| 272 |
)
|
| 273 |
gr.on(
|
|
|
|
| 276 |
inputs=[allow_deleting_runs, runs_table, project_dd],
|
| 277 |
outputs=[runs_table],
|
| 278 |
show_progress="hidden",
|
| 279 |
+
api_visibility="private",
|
| 280 |
queue=False,
|
| 281 |
)
|
utils.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
| 3 |
import re
|
| 4 |
import time
|
| 5 |
from datetime import datetime, timezone
|
|
|
|
| 6 |
from pathlib import Path
|
| 7 |
from typing import TYPE_CHECKING
|
| 8 |
|
|
@@ -144,7 +145,7 @@ def generate_readable_name(used_names: list[str], space_id: str | None = None) -
|
|
| 144 |
If space_id is provided, generates username-timestamp format instead.
|
| 145 |
"""
|
| 146 |
if space_id is not None:
|
| 147 |
-
username =
|
| 148 |
timestamp = int(time.time())
|
| 149 |
return f"{username}-{timestamp}"
|
| 150 |
adjectives = [
|
|
@@ -418,10 +419,10 @@ def preprocess_space_and_dataset_ids(
|
|
| 418 |
space_id: str | None, dataset_id: str | None
|
| 419 |
) -> tuple[str | None, str | None]:
|
| 420 |
if space_id is not None and "/" not in space_id:
|
| 421 |
-
username =
|
| 422 |
space_id = f"{username}/{space_id}"
|
| 423 |
if dataset_id is not None and "/" not in dataset_id:
|
| 424 |
-
username =
|
| 425 |
dataset_id = f"{username}/{dataset_id}"
|
| 426 |
if space_id is not None and dataset_id is None:
|
| 427 |
dataset_id = f"{space_id}-dataset"
|
|
@@ -465,26 +466,39 @@ def format_timestamp(timestamp_str):
|
|
| 465 |
return "Unknown"
|
| 466 |
|
| 467 |
|
| 468 |
-
|
|
|
|
|
|
|
| 469 |
"#3B82F6",
|
| 470 |
-
"#EF4444",
|
| 471 |
"#10B981",
|
| 472 |
-
"#
|
| 473 |
"#8B5CF6",
|
|
|
|
|
|
|
| 474 |
"#EC4899",
|
| 475 |
"#06B6D4",
|
| 476 |
-
"#84CC16",
|
| 477 |
-
"#F97316",
|
| 478 |
-
"#6366F1",
|
| 479 |
]
|
| 480 |
|
| 481 |
|
| 482 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
"""Generate color mapping for runs, with transparency for original data when smoothing is enabled."""
|
|
|
|
|
|
|
|
|
|
| 484 |
color_map = {}
|
| 485 |
|
| 486 |
for i, run in enumerate(runs):
|
| 487 |
-
base_color =
|
| 488 |
|
| 489 |
if smoothing:
|
| 490 |
color_map[run] = base_color + "4D"
|
|
@@ -802,11 +816,15 @@ def deserialize_values(metrics):
|
|
| 802 |
return result
|
| 803 |
|
| 804 |
|
| 805 |
-
def get_full_url(
|
|
|
|
|
|
|
| 806 |
params = []
|
| 807 |
if project:
|
| 808 |
params.append(f"project={project}")
|
| 809 |
params.append(f"write_token={write_token}")
|
|
|
|
|
|
|
| 810 |
return base_url + "?" + "&".join(params)
|
| 811 |
|
| 812 |
|
|
@@ -852,3 +870,17 @@ def get_space() -> str | None:
|
|
| 852 |
def ordered_subset(items: list[str], subset: list[str] | None) -> list[str]:
|
| 853 |
subset_set = set(subset or [])
|
| 854 |
return [item for item in items if item in subset_set]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import re
|
| 4 |
import time
|
| 5 |
from datetime import datetime, timezone
|
| 6 |
+
from functools import lru_cache
|
| 7 |
from pathlib import Path
|
| 8 |
from typing import TYPE_CHECKING
|
| 9 |
|
|
|
|
| 145 |
If space_id is provided, generates username-timestamp format instead.
|
| 146 |
"""
|
| 147 |
if space_id is not None:
|
| 148 |
+
username = _get_default_namespace()
|
| 149 |
timestamp = int(time.time())
|
| 150 |
return f"{username}-{timestamp}"
|
| 151 |
adjectives = [
|
|
|
|
| 419 |
space_id: str | None, dataset_id: str | None
|
| 420 |
) -> tuple[str | None, str | None]:
|
| 421 |
if space_id is not None and "/" not in space_id:
|
| 422 |
+
username = _get_default_namespace()
|
| 423 |
space_id = f"{username}/{space_id}"
|
| 424 |
if dataset_id is not None and "/" not in dataset_id:
|
| 425 |
+
username = _get_default_namespace()
|
| 426 |
dataset_id = f"{username}/{dataset_id}"
|
| 427 |
if space_id is not None and dataset_id is None:
|
| 428 |
dataset_id = f"{space_id}-dataset"
|
|
|
|
| 466 |
return "Unknown"
|
| 467 |
|
| 468 |
|
| 469 |
+
DEFAULT_COLOR_PALETTE = [
|
| 470 |
+
"#A8769B",
|
| 471 |
+
"#E89957",
|
| 472 |
"#3B82F6",
|
|
|
|
| 473 |
"#10B981",
|
| 474 |
+
"#EF4444",
|
| 475 |
"#8B5CF6",
|
| 476 |
+
"#14B8A6",
|
| 477 |
+
"#F59E0B",
|
| 478 |
"#EC4899",
|
| 479 |
"#06B6D4",
|
|
|
|
|
|
|
|
|
|
| 480 |
]
|
| 481 |
|
| 482 |
|
| 483 |
+
def get_color_palette() -> list[str]:
|
| 484 |
+
"""Get the color palette from environment variable or use default."""
|
| 485 |
+
env_palette = os.environ.get("TRACKIO_COLOR_PALETTE")
|
| 486 |
+
if env_palette:
|
| 487 |
+
return [color.strip() for color in env_palette.split(",")]
|
| 488 |
+
return DEFAULT_COLOR_PALETTE
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def get_color_mapping(
|
| 492 |
+
runs: list[str], smoothing: bool, color_palette: list[str] | None = None
|
| 493 |
+
) -> dict[str, str]:
|
| 494 |
"""Generate color mapping for runs, with transparency for original data when smoothing is enabled."""
|
| 495 |
+
if color_palette is None:
|
| 496 |
+
color_palette = get_color_palette()
|
| 497 |
+
|
| 498 |
color_map = {}
|
| 499 |
|
| 500 |
for i, run in enumerate(runs):
|
| 501 |
+
base_color = color_palette[i % len(color_palette)]
|
| 502 |
|
| 503 |
if smoothing:
|
| 504 |
color_map[run] = base_color + "4D"
|
|
|
|
| 816 |
return result
|
| 817 |
|
| 818 |
|
| 819 |
+
def get_full_url(
|
| 820 |
+
base_url: str, project: str | None, write_token: str, footer: bool = True
|
| 821 |
+
) -> str:
|
| 822 |
params = []
|
| 823 |
if project:
|
| 824 |
params.append(f"project={project}")
|
| 825 |
params.append(f"write_token={write_token}")
|
| 826 |
+
if not footer:
|
| 827 |
+
params.append("footer=false")
|
| 828 |
return base_url + "?" + "&".join(params)
|
| 829 |
|
| 830 |
|
|
|
|
| 870 |
def ordered_subset(items: list[str], subset: list[str] | None) -> list[str]:
|
| 871 |
subset_set = set(subset or [])
|
| 872 |
return [item for item in items if item in subset_set]
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
def _get_default_namespace() -> str:
|
| 876 |
+
"""Get the default namespace (username).
|
| 877 |
+
|
| 878 |
+
This function uses caching to avoid repeated API calls to /whoami-v2.
|
| 879 |
+
"""
|
| 880 |
+
token = huggingface_hub.get_token()
|
| 881 |
+
return _cached_whoami(token)["name"]
|
| 882 |
+
|
| 883 |
+
|
| 884 |
+
@lru_cache(maxsize=32)
|
| 885 |
+
def _cached_whoami(token: str | None) -> dict:
|
| 886 |
+
return huggingface_hub.whoami(token=token)
|