abidlabs HF Staff commited on
Commit
f5ab433
·
verified ·
1 Parent(s): 81f8012

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ trackio_logo.png filter=lfs diff=lfs merge=lfs -text
__init__.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import contextvars
2
+ import time
3
+ import webbrowser
4
+ from pathlib import Path
5
+
6
+ import huggingface_hub
7
+ from gradio_client import Client
8
+ from httpx import ReadTimeout
9
+ from huggingface_hub.errors import RepositoryNotFoundError
10
+
11
+ from trackio.deploy import deploy_as_space
12
+ from trackio.run import Run
13
+ from trackio.ui import demo
14
+ from trackio.utils import TRACKIO_DIR, TRACKIO_LOGO_PATH, block_except_in_notebook
15
+
16
+ __version__ = Path(__file__).parent.joinpath("version.txt").read_text().strip()
17
+
18
+
19
+ current_run: contextvars.ContextVar[Run | None] = contextvars.ContextVar(
20
+ "current_run", default=None
21
+ )
22
+ current_project: contextvars.ContextVar[str | None] = contextvars.ContextVar(
23
+ "current_project", default=None
24
+ )
25
+ current_server: contextvars.ContextVar[str | None] = contextvars.ContextVar(
26
+ "current_server", default=None
27
+ )
28
+
29
+ config = {}
30
+ SPACE_URL = "https://huggingface.co/spaces/{space_id}"
31
+
32
+
33
+ def init(
34
+ project: str,
35
+ name: str | None = None,
36
+ space_id: str | None = None,
37
+ dataset_id: str | None = None,
38
+ config: dict | None = None,
39
+ ) -> Run:
40
+ """
41
+ Creates a new Trackio project and returns a Run object.
42
+
43
+ Args:
44
+ project: The name of the project (can be an existing project to continue tracking or a new project to start tracking from scratch).
45
+ name: The name of the run (if not provided, a default name will be generated).
46
+ space_id: If provided, the project will be logged to a Hugging Face Space instead of a local directory. Should be a complete Space name like "username/reponame". If the Space does not exist, it will be created. If the Space already exists, the project will be logged to it.
47
+ dataset_id: If provided, a persistent Hugging Face Dataset will be created and the metrics will be synced to it every 5 minutes. Should be a complete Dataset name like "username/datasetname". If the Dataset does not exist, it will be created. If the Dataset already exists, the project will be appended to it.
48
+ config: A dictionary of configuration options. Provided for compatibility with wandb.init()
49
+ """
50
+ if not current_server.get() and space_id is None:
51
+ _, url, _ = demo.launch(
52
+ show_api=False, inline=False, quiet=True, prevent_thread_lock=True
53
+ )
54
+ current_server.set(url)
55
+ else:
56
+ url = current_server.get()
57
+
58
+ if current_project.get() is None or current_project.get() != project:
59
+ print(f"* Trackio project initialized: {project}")
60
+
61
+ if space_id is None:
62
+ print(f"* Trackio metrics logged to: {TRACKIO_DIR}")
63
+ print(
64
+ f'\n* View dashboard by running in your terminal: trackio show --project "{project}"'
65
+ )
66
+ print(f'* or by running in Python: trackio.show(project="{project}")')
67
+ else:
68
+ create_space_if_not_exists(space_id, dataset_id)
69
+ print(
70
+ f"* View dashboard by going to: {SPACE_URL.format(space_id=space_id)}"
71
+ )
72
+ current_project.set(project)
73
+
74
+ space_or_url = space_id if space_id else url
75
+ client = Client(space_or_url, verbose=False)
76
+ run = Run(
77
+ project=project, client=client, name=name, config=config, dataset_id=dataset_id
78
+ )
79
+ current_run.set(run)
80
+ globals()["config"] = run.config
81
+ return run
82
+
83
+
84
+ def create_space_if_not_exists(
85
+ space_id: str,
86
+ dataset_id: str | None = None,
87
+ ) -> None:
88
+ """
89
+ Creates a new Hugging Face Space if it does not exist.
90
+
91
+ Args:
92
+ space_id: The ID of the Space to create.
93
+ dataset_id: The ID of the Dataset to create.
94
+ """
95
+ if "/" not in space_id:
96
+ raise ValueError(
97
+ f"Invalid space ID: {space_id}. Must be in the format: username/reponame."
98
+ )
99
+ if dataset_id is not None and "/" not in dataset_id:
100
+ raise ValueError(
101
+ f"Invalid dataset ID: {dataset_id}. Must be in the format: username/datasetname."
102
+ )
103
+ try:
104
+ huggingface_hub.repo_info(space_id, repo_type="space")
105
+ print(f"* Found existing space: {SPACE_URL.format(space_id=space_id)}")
106
+ return
107
+ except RepositoryNotFoundError:
108
+ pass
109
+
110
+ print(f"* Creating new space: {SPACE_URL.format(space_id=space_id)}")
111
+ deploy_as_space(space_id, dataset_id)
112
+
113
+ client = None
114
+ for _ in range(30):
115
+ try:
116
+ client = Client(space_id, verbose=False)
117
+ if client:
118
+ break
119
+ except ReadTimeout:
120
+ print("* Space is not yet ready. Waiting 5 seconds...")
121
+ time.sleep(5)
122
+ except ValueError as e:
123
+ print(f"* Space gave error {e}. Trying again in 5 seconds...")
124
+ time.sleep(5)
125
+
126
+
127
+ def log(metrics: dict) -> None:
128
+ """
129
+ Logs metrics to the current run.
130
+
131
+ Args:
132
+ metrics: A dictionary of metrics to log.
133
+ """
134
+ if current_run.get() is None:
135
+ raise RuntimeError("Call trackio.init() before log().")
136
+ current_run.get().log(metrics)
137
+
138
+
139
+ def finish():
140
+ """
141
+ Finishes the current run.
142
+ """
143
+ if current_run.get() is None:
144
+ raise RuntimeError("Call trackio.init() before finish().")
145
+ current_run.get().finish()
146
+
147
+
148
+ def show(project: str | None = None):
149
+ """
150
+ Launches the Trackio dashboard.
151
+
152
+ Args:
153
+ project: The name of the project whose runs to show. If not provided, all projects will be shown and the user can select one.
154
+ """
155
+ _, url, share_url = demo.launch(
156
+ show_api=False,
157
+ quiet=True,
158
+ inline=False,
159
+ prevent_thread_lock=True,
160
+ favicon_path=TRACKIO_LOGO_PATH,
161
+ allowed_paths=[TRACKIO_LOGO_PATH],
162
+ )
163
+ base_url = share_url + "/" if share_url else url
164
+ dashboard_url = base_url + f"?project={project}" if project else base_url
165
+ print(f"* Trackio UI launched at: {dashboard_url}")
166
+ webbrowser.open(dashboard_url)
167
+ block_except_in_notebook()
__pycache__/__init__.cpython-310.pyc ADDED
Binary file (4.96 kB). View file
 
__pycache__/__init__.cpython-312.pyc ADDED
Binary file (7.73 kB). View file
 
__pycache__/cli.cpython-312.pyc ADDED
Binary file (1.11 kB). View file
 
__pycache__/context.cpython-312.pyc ADDED
Binary file (440 Bytes). View file
 
__pycache__/deploy.cpython-310.pyc ADDED
Binary file (1.72 kB). View file
 
__pycache__/deploy.cpython-312.pyc ADDED
Binary file (2.72 kB). View file
 
__pycache__/dummy_commit_scheduler.cpython-310.pyc ADDED
Binary file (936 Bytes). View file
 
__pycache__/dummy_commit_scheduler.cpython-312.pyc ADDED
Binary file (1.01 kB). View file
 
__pycache__/run.cpython-310.pyc ADDED
Binary file (1.01 kB). View file
 
__pycache__/run.cpython-312.pyc ADDED
Binary file (1.4 kB). View file
 
__pycache__/sqlite_storage.cpython-310.pyc ADDED
Binary file (5.37 kB). View file
 
__pycache__/sqlite_storage.cpython-312.pyc ADDED
Binary file (8.23 kB). View file
 
__pycache__/storage.cpython-312.pyc ADDED
Binary file (4.6 kB). View file
 
__pycache__/ui.cpython-310.pyc ADDED
Binary file (7.83 kB). View file
 
__pycache__/ui.cpython-312.pyc ADDED
Binary file (12.9 kB). View file
 
__pycache__/utils.cpython-310.pyc ADDED
Binary file (2.62 kB). View file
 
__pycache__/utils.cpython-312.pyc ADDED
Binary file (3.19 kB). View file
 
cli.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ from trackio import show
4
+
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(description="Trackio CLI")
8
+ subparsers = parser.add_subparsers(dest="command")
9
+
10
+ ui_parser = subparsers.add_parser(
11
+ "show", help="Show the Trackio dashboard UI for a project"
12
+ )
13
+ ui_parser.add_argument(
14
+ "--project", required=False, help="Project name to show in the dashboard"
15
+ )
16
+
17
+ args = parser.parse_args()
18
+
19
+ if args.command == "show":
20
+ show(args.project)
21
+ else:
22
+ parser.print_help()
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main()
deploy.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ from importlib.resources import files
4
+ from pathlib import Path
5
+
6
+ import gradio
7
+ import huggingface_hub
8
+
9
+
10
+ def deploy_as_space(
11
+ title: str,
12
+ dataset_id: str | None = None,
13
+ ):
14
+ if (
15
+ os.getenv("SYSTEM") == "spaces"
16
+ ): # in case a repo with this function is uploaded to spaces
17
+ return
18
+
19
+ trackio_path = files("trackio")
20
+
21
+ hf_api = huggingface_hub.HfApi()
22
+ whoami = None
23
+ login = False
24
+ try:
25
+ whoami = hf_api.whoami()
26
+ if whoami["auth"]["accessToken"]["role"] != "write":
27
+ login = True
28
+ except OSError:
29
+ login = True
30
+ if login:
31
+ print("Need 'write' access token to create a Spaces repo.")
32
+ huggingface_hub.login(add_to_git_credential=False)
33
+ whoami = hf_api.whoami()
34
+
35
+ space_id = huggingface_hub.create_repo(
36
+ title,
37
+ space_sdk="gradio",
38
+ repo_type="space",
39
+ exist_ok=True,
40
+ ).repo_id
41
+ assert space_id == title # not sure why these would differ
42
+
43
+ with open(Path(trackio_path, "README.md"), "r") as f:
44
+ readme_content = f.read()
45
+ readme_content = readme_content.replace("{GRADIO_VERSION}", gradio.__version__)
46
+ readme_buffer = io.BytesIO(readme_content.encode("utf-8"))
47
+ hf_api.upload_file(
48
+ path_or_fileobj=readme_buffer,
49
+ path_in_repo="README.md",
50
+ repo_id=space_id,
51
+ repo_type="space",
52
+ )
53
+
54
+ huggingface_hub.utils.disable_progress_bars()
55
+ hf_api.upload_folder(
56
+ repo_id=space_id,
57
+ repo_type="space",
58
+ folder_path=trackio_path,
59
+ ignore_patterns=["README.md"],
60
+ )
61
+
62
+ hf_token = huggingface_hub.utils.get_token()
63
+ if hf_token is not None:
64
+ huggingface_hub.add_space_secret(space_id, "HF_TOKEN", hf_token)
65
+ if dataset_id is not None:
66
+ huggingface_hub.add_space_variable(space_id, "TRACKIO_DATASET_ID", dataset_id)
67
+ # So that the dataset id is available to the sqlite_storage.py file
68
+ # if running locally as well.
69
+ os.environ["TRACKIO_DATASET_ID"] = dataset_id
dummy_commit_scheduler.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # A dummy object to fit the interface of huggingface_hub's CommitScheduler
2
+ class DummyCommitSchedulerLock:
3
+ def __enter__(self):
4
+ return None
5
+
6
+ def __exit__(self, exception_type, exception_value, exception_traceback):
7
+ pass
8
+
9
+
10
+ class DummyCommitScheduler:
11
+ def __init__(self):
12
+ self.lock = DummyCommitSchedulerLock()
run.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gradio_client import Client
2
+
3
+ from trackio.utils import generate_readable_name
4
+
5
+
6
+ class Run:
7
+ def __init__(
8
+ self,
9
+ project: str,
10
+ client: Client,
11
+ name: str | None = None,
12
+ config: dict | None = None,
13
+ dataset_id: str | None = None,
14
+ ):
15
+ self.project = project
16
+ self.client = client
17
+ self.name = name or generate_readable_name()
18
+ self.config = config or {}
19
+ self.dataset_id = dataset_id
20
+
21
+ def log(self, metrics: dict):
22
+ self.client.predict(
23
+ api_name="/log",
24
+ project=self.project,
25
+ run=self.name,
26
+ metrics=metrics,
27
+ dataset_id=self.dataset_id,
28
+ )
29
+
30
+ def finish(self):
31
+ pass
sqlite_storage.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import sqlite3
4
+
5
+ from huggingface_hub import CommitScheduler
6
+
7
+ try:
8
+ from trackio.dummy_commit_scheduler import DummyCommitScheduler
9
+ from trackio.utils import RESERVED_KEYS, TRACKIO_DIR
10
+ except: # noqa: E722
11
+ from dummy_commit_scheduler import DummyCommitScheduler
12
+ from utils import RESERVED_KEYS, TRACKIO_DIR
13
+
14
+
15
+ class SQLiteStorage:
16
+ def __init__(
17
+ self, project: str, name: str, config: dict, dataset_id: str | None = None
18
+ ):
19
+ self.project = project
20
+ self.name = name
21
+ self.config = config
22
+ self.db_path = os.path.join(TRACKIO_DIR, "trackio.db")
23
+ self.dataset_id = dataset_id
24
+ self.scheduler = self._get_scheduler()
25
+
26
+ os.makedirs(TRACKIO_DIR, exist_ok=True)
27
+
28
+ self._init_db()
29
+ self._save_config()
30
+
31
+ def _get_scheduler(self):
32
+ hf_token = os.environ.get(
33
+ "HF_TOKEN"
34
+ ) # Get the token from the environment variable on Spaces
35
+ dataset_id = self.dataset_id or os.environ.get("TRACKIO_DATASET_ID")
36
+ if dataset_id is None:
37
+ scheduler = DummyCommitScheduler()
38
+ else:
39
+ scheduler = CommitScheduler(
40
+ repo_id=dataset_id,
41
+ repo_type="dataset",
42
+ folder_path=TRACKIO_DIR,
43
+ private=True,
44
+ squash_history=True,
45
+ token=hf_token,
46
+ )
47
+ return scheduler
48
+
49
+ def _init_db(self):
50
+ """Initialize the SQLite database with required tables."""
51
+ with self.scheduler.lock:
52
+ with sqlite3.connect(self.db_path) as conn:
53
+ cursor = conn.cursor()
54
+
55
+ cursor.execute("""
56
+ CREATE TABLE IF NOT EXISTS metrics (
57
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
58
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
59
+ project_name TEXT NOT NULL,
60
+ run_name TEXT NOT NULL,
61
+ metrics TEXT NOT NULL
62
+ )
63
+ """)
64
+
65
+ cursor.execute("""
66
+ CREATE TABLE IF NOT EXISTS configs (
67
+ project_name TEXT NOT NULL,
68
+ run_name TEXT NOT NULL,
69
+ config TEXT NOT NULL,
70
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
71
+ PRIMARY KEY (project_name, run_name)
72
+ )
73
+ """)
74
+
75
+ conn.commit()
76
+
77
+ def _save_config(self):
78
+ """Save the run configuration to the database."""
79
+ with self.scheduler.lock:
80
+ with sqlite3.connect(self.db_path) as conn:
81
+ cursor = conn.cursor()
82
+ cursor.execute(
83
+ "INSERT OR REPLACE INTO configs (project_name, run_name, config) VALUES (?, ?, ?)",
84
+ (self.project, self.name, json.dumps(self.config)),
85
+ )
86
+ conn.commit()
87
+
88
+ def log(self, metrics: dict):
89
+ """Log metrics to the database."""
90
+ for k in metrics.keys():
91
+ if k in RESERVED_KEYS or k.startswith("__"):
92
+ raise ValueError(
93
+ f"Please do not use this reserved key as a metric: {k}"
94
+ )
95
+
96
+ with self.scheduler.lock:
97
+ with sqlite3.connect(self.db_path) as conn:
98
+ cursor = conn.cursor()
99
+ cursor.execute(
100
+ """
101
+ INSERT INTO metrics
102
+ (project_name, run_name, metrics)
103
+ VALUES (?, ?, ?)
104
+ """,
105
+ (self.project, self.name, json.dumps(metrics)),
106
+ )
107
+ conn.commit()
108
+
109
+ def get_metrics(self, project: str, run: str) -> list[dict]:
110
+ """Retrieve metrics for a specific run."""
111
+ with sqlite3.connect(self.db_path) as conn:
112
+ cursor = conn.cursor()
113
+ cursor.execute(
114
+ """
115
+ SELECT timestamp, metrics
116
+ FROM metrics
117
+ WHERE project_name = ? AND run_name = ?
118
+ ORDER BY timestamp
119
+ """,
120
+ (project, run),
121
+ )
122
+ rows = cursor.fetchall()
123
+
124
+ results = []
125
+ for row in rows:
126
+ timestamp, metrics_json = row
127
+ metrics = json.loads(metrics_json)
128
+ metrics["timestamp"] = timestamp
129
+ results.append(metrics)
130
+
131
+ return results
132
+
133
+ def get_projects(self) -> list[str]:
134
+ """Get list of all projects."""
135
+ with sqlite3.connect(self.db_path) as conn:
136
+ cursor = conn.cursor()
137
+ cursor.execute("SELECT DISTINCT project_name FROM metrics")
138
+ return [row[0] for row in cursor.fetchall()]
139
+
140
+ def get_runs(self, project: str) -> list[str]:
141
+ """Get list of all runs for a project."""
142
+ with sqlite3.connect(self.db_path) as conn:
143
+ cursor = conn.cursor()
144
+ cursor.execute(
145
+ "SELECT DISTINCT run_name FROM metrics WHERE project_name = ?",
146
+ (project,),
147
+ )
148
+ return [row[0] for row in cursor.fetchall()]
149
+
150
+ def finish(self):
151
+ """Cleanup when run is finished."""
152
+ pass
trackio_logo.png ADDED

Git LFS Details

  • SHA256: 3922c4d1e465270ad4d8abb12023f3beed5d9f7f338528a4c0ac21dcf358a1c8
  • Pointer size: 131 Bytes
  • Size of remote file: 487 kB
ui.py ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Any
3
+
4
+ import gradio as gr
5
+ import pandas as pd
6
+
7
+ try:
8
+ from trackio.sqlite_storage import SQLiteStorage
9
+ from trackio.utils import RESERVED_KEYS, TRACKIO_LOGO_PATH
10
+ except: # noqa: E722
11
+ from sqlite_storage import SQLiteStorage
12
+ from utils import RESERVED_KEYS, TRACKIO_LOGO_PATH
13
+
14
+ css = """
15
+ #run-cb .wrap {
16
+ gap: 2px;
17
+ }
18
+ #run-cb .wrap label {
19
+ line-height: 1;
20
+ padding: 6px;
21
+ }
22
+ """
23
+
24
+ COLOR_PALETTE = [
25
+ "#3B82F6",
26
+ "#EF4444",
27
+ "#10B981",
28
+ "#F59E0B",
29
+ "#8B5CF6",
30
+ "#EC4899",
31
+ "#06B6D4",
32
+ "#84CC16",
33
+ "#F97316",
34
+ "#6366F1",
35
+ ]
36
+
37
+
38
+ def get_color_mapping(runs: list[str], smoothing: bool) -> dict[str, str]:
39
+ """Generate color mapping for runs, with transparency for original data when smoothing is enabled."""
40
+ color_map = {}
41
+
42
+ for i, run in enumerate(runs):
43
+ base_color = COLOR_PALETTE[i % len(COLOR_PALETTE)]
44
+
45
+ if smoothing:
46
+ color_map[f"{run}_smoothed"] = base_color
47
+ color_map[f"{run}_original"] = base_color + "4D"
48
+ else:
49
+ color_map[run] = base_color
50
+
51
+ return color_map
52
+
53
+
54
+ def get_projects(request: gr.Request):
55
+ dataset_id = os.environ.get("TRACKIO_DATASET_ID", "abidlabs/metrics")
56
+ storage = SQLiteStorage("", "", {}, dataset_id=dataset_id)
57
+ projects = storage.get_projects()
58
+ if project := request.query_params.get("project"):
59
+ interactive = False
60
+ else:
61
+ interactive = True
62
+ project = projects[0] if projects else None
63
+ return gr.Dropdown(
64
+ label="Project",
65
+ choices=projects,
66
+ value=project,
67
+ allow_custom_value=True,
68
+ interactive=interactive,
69
+ info=f"&#x21bb; Synced to <a href='https://huggingface.co/{dataset_id}' target='_blank'>{dataset_id}</a> every 5 min"
70
+ if dataset_id
71
+ else None,
72
+ )
73
+
74
+
75
+ def get_runs(project):
76
+ if not project:
77
+ return []
78
+ storage = SQLiteStorage("", "", {})
79
+ return storage.get_runs(project)
80
+
81
+
82
+ def load_run_data(project: str | None, run: str | None, smoothing: bool):
83
+ if not project or not run:
84
+ return None
85
+ storage = SQLiteStorage("", "", {})
86
+ metrics = storage.get_metrics(project, run)
87
+ if not metrics:
88
+ return None
89
+ df = pd.DataFrame(metrics)
90
+
91
+ if "step" not in df.columns:
92
+ df["step"] = range(len(df))
93
+
94
+ if smoothing:
95
+ numeric_cols = df.select_dtypes(include="number").columns
96
+ numeric_cols = [c for c in numeric_cols if c not in RESERVED_KEYS]
97
+
98
+ df_original = df.copy()
99
+ df_original["run"] = f"{run}_original"
100
+ df_original["data_type"] = "original"
101
+
102
+ df_smoothed = df.copy()
103
+ df_smoothed[numeric_cols] = df_smoothed[numeric_cols].ewm(alpha=0.1).mean()
104
+ df_smoothed["run"] = f"{run}_smoothed"
105
+ df_smoothed["data_type"] = "smoothed"
106
+
107
+ combined_df = pd.concat([df_original, df_smoothed], ignore_index=True)
108
+ return combined_df
109
+ else:
110
+ df["run"] = run
111
+ df["data_type"] = "original"
112
+ return df
113
+
114
+
115
+ def update_runs(project, filter_text, user_interacted_with_runs=False):
116
+ if project is None:
117
+ runs = []
118
+ num_runs = 0
119
+ else:
120
+ runs = get_runs(project)
121
+ num_runs = len(runs)
122
+ if filter_text:
123
+ runs = [r for r in runs if filter_text in r]
124
+ if not user_interacted_with_runs:
125
+ return gr.CheckboxGroup(
126
+ choices=runs, value=[runs[0]] if runs else []
127
+ ), gr.Textbox(label=f"Runs ({num_runs})")
128
+ else:
129
+ return gr.CheckboxGroup(choices=runs), gr.Textbox(label=f"Runs ({num_runs})")
130
+
131
+
132
+ def filter_runs(project, filter_text):
133
+ runs = get_runs(project)
134
+ runs = [r for r in runs if filter_text in r]
135
+ return gr.CheckboxGroup(choices=runs, value=runs)
136
+
137
+
138
+ def toggle_timer(cb_value):
139
+ if cb_value:
140
+ return gr.Timer(active=True)
141
+ else:
142
+ return gr.Timer(active=False)
143
+
144
+
145
+ def log(project: str, run: str, metrics: dict[str, Any], dataset_id: str) -> None:
146
+ # Note: the type hint for dataset_id should be str | None but gr.api
147
+ # doesn't support that, see: https://github.com/gradio-app/gradio/issues/11175#issuecomment-2920203317
148
+ storage = SQLiteStorage(project, run, {}, dataset_id=dataset_id)
149
+ storage.log(metrics)
150
+
151
+
152
+ def sort_metrics_by_prefix(metrics: list[str]) -> list[str]:
153
+ """
154
+ Sort metrics by grouping prefixes together.
155
+ Metrics without prefixes come first, then grouped by prefix.
156
+
157
+ Example:
158
+ Input: ["train/loss", "loss", "train/acc", "val/loss"]
159
+ Output: ["loss", "train/acc", "train/loss", "val/loss"]
160
+ """
161
+ no_prefix = []
162
+ with_prefix = []
163
+
164
+ for metric in metrics:
165
+ if "/" in metric:
166
+ with_prefix.append(metric)
167
+ else:
168
+ no_prefix.append(metric)
169
+
170
+ no_prefix.sort()
171
+
172
+ prefix_groups = {}
173
+ for metric in with_prefix:
174
+ prefix = metric.split("/")[0]
175
+ if prefix not in prefix_groups:
176
+ prefix_groups[prefix] = []
177
+ prefix_groups[prefix].append(metric)
178
+
179
+ sorted_with_prefix = []
180
+ for prefix in sorted(prefix_groups.keys()):
181
+ sorted_with_prefix.extend(sorted(prefix_groups[prefix]))
182
+
183
+ return no_prefix + sorted_with_prefix
184
+
185
+
186
+ def configure(request: gr.Request):
187
+ if metrics := request.query_params.get("metrics"):
188
+ return metrics.split(",")
189
+ else:
190
+ return []
191
+
192
+
193
+ with gr.Blocks(theme="citrus", title="Trackio Dashboard", css=css) as demo:
194
+ with gr.Sidebar() as sidebar:
195
+ gr.Markdown(
196
+ f"<div style='display: flex; align-items: center; gap: 8px;'><img src='/gradio_api/file={TRACKIO_LOGO_PATH}' width='32' height='32'><span style='font-size: 2em; font-weight: bold;'>Trackio</span></div>"
197
+ )
198
+ project_dd = gr.Dropdown(label="Project")
199
+ run_tb = gr.Textbox(label="Runs", placeholder="Type to filter...")
200
+ run_cb = gr.CheckboxGroup(
201
+ label="Runs", choices=[], interactive=True, elem_id="run-cb"
202
+ )
203
+
204
+ with gr.Sidebar(position="right", open=False) as settings_sidebar:
205
+ gr.Markdown("### ⚙️ Settings")
206
+ realtime_cb = gr.Checkbox(label="Refresh realtime", value=True)
207
+ smoothing_cb = gr.Checkbox(label="Smoothing", value=True)
208
+
209
+ timer = gr.Timer(value=1)
210
+ metrics_subset = gr.State([])
211
+ user_interacted_with_run_cb = gr.State(False)
212
+
213
+ gr.on(
214
+ [demo.load],
215
+ fn=configure,
216
+ outputs=metrics_subset,
217
+ )
218
+ gr.on(
219
+ [demo.load],
220
+ fn=get_projects,
221
+ outputs=project_dd,
222
+ show_progress="hidden",
223
+ )
224
+ gr.on(
225
+ [timer.tick],
226
+ fn=update_runs,
227
+ inputs=[project_dd, run_tb, user_interacted_with_run_cb],
228
+ outputs=[run_cb, run_tb],
229
+ show_progress="hidden",
230
+ )
231
+ gr.on(
232
+ [demo.load, project_dd.change],
233
+ fn=update_runs,
234
+ inputs=[project_dd, run_tb],
235
+ outputs=[run_cb, run_tb],
236
+ show_progress="hidden",
237
+ )
238
+
239
+ realtime_cb.change(
240
+ fn=toggle_timer,
241
+ inputs=realtime_cb,
242
+ outputs=timer,
243
+ api_name="toggle_timer",
244
+ )
245
+ run_cb.input(
246
+ fn=lambda: True,
247
+ outputs=user_interacted_with_run_cb,
248
+ )
249
+ run_tb.input(
250
+ fn=filter_runs,
251
+ inputs=[project_dd, run_tb],
252
+ outputs=run_cb,
253
+ )
254
+
255
+ gr.api(
256
+ fn=log,
257
+ api_name="log",
258
+ )
259
+
260
+ x_lim = gr.State(None)
261
+
262
+ def update_x_lim(select_data: gr.SelectData):
263
+ return select_data.index
264
+
265
+ @gr.render(
266
+ triggers=[
267
+ demo.load,
268
+ run_cb.change,
269
+ timer.tick,
270
+ smoothing_cb.change,
271
+ x_lim.change,
272
+ ],
273
+ inputs=[project_dd, run_cb, smoothing_cb, metrics_subset, x_lim],
274
+ )
275
+ def update_dashboard(project, runs, smoothing, metrics_subset, x_lim_value):
276
+ dfs = []
277
+ original_runs = runs.copy()
278
+
279
+ for run in runs:
280
+ df = load_run_data(project, run, smoothing)
281
+ if df is not None:
282
+ dfs.append(df)
283
+
284
+ if dfs:
285
+ master_df = pd.concat(dfs, ignore_index=True)
286
+ else:
287
+ master_df = pd.DataFrame()
288
+
289
+ if master_df.empty:
290
+ return
291
+
292
+ numeric_cols = master_df.select_dtypes(include="number").columns
293
+ numeric_cols = [
294
+ c for c in numeric_cols if c not in RESERVED_KEYS and c != "step"
295
+ ]
296
+ if metrics_subset:
297
+ numeric_cols = [c for c in numeric_cols if c in metrics_subset]
298
+ numeric_cols = sort_metrics_by_prefix(list(numeric_cols))
299
+
300
+ color_map = get_color_mapping(original_runs, smoothing)
301
+
302
+ plots: list[gr.LinePlot] = []
303
+ for col in range((len(numeric_cols) + 1) // 2):
304
+ with gr.Row(key=f"row-{col}"):
305
+ for i in range(2):
306
+ metric_idx = 2 * col + i
307
+ if metric_idx < len(numeric_cols):
308
+ metric_name = numeric_cols[metric_idx]
309
+
310
+ metric_df = master_df.dropna(subset=[metric_name])
311
+
312
+ if not metric_df.empty:
313
+ plot = gr.LinePlot(
314
+ metric_df,
315
+ x="step",
316
+ y=metric_name,
317
+ color="run" if "run" in metric_df.columns else None,
318
+ color_map=color_map,
319
+ title=metric_name,
320
+ key=f"plot-{col}-{i}",
321
+ preserved_by_key=None,
322
+ x_lim=x_lim_value,
323
+ y_lim=[
324
+ metric_df[metric_name].min(),
325
+ metric_df[metric_name].max(),
326
+ ],
327
+ show_fullscreen_button=True,
328
+ )
329
+ plots.append(plot)
330
+
331
+ for plot in plots:
332
+ plot.select(update_x_lim, outputs=x_lim)
333
+ plot.double_click(lambda: None, outputs=x_lim)
334
+
335
+
336
+ if __name__ == "__main__":
337
+ demo.launch(allowed_paths=[TRACKIO_LOGO_PATH], show_api=False)
utils.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import sys
4
+ import time
5
+ from pathlib import Path
6
+
7
+ from huggingface_hub.constants import HF_HOME
8
+
9
+ RESERVED_KEYS = ["project", "run", "timestamp", "step"]
10
+ TRACKIO_DIR = os.path.join(HF_HOME, "trackio")
11
+
12
+ TRACKIO_LOGO_PATH = str(Path(__file__).parent.joinpath("trackio_logo.png"))
13
+
14
+
15
+ def generate_readable_name():
16
+ """
17
+ Generates a random, readable name like "dainty-sunset-1"
18
+ """
19
+ adjectives = [
20
+ "dainty",
21
+ "brave",
22
+ "calm",
23
+ "eager",
24
+ "fancy",
25
+ "gentle",
26
+ "happy",
27
+ "jolly",
28
+ "kind",
29
+ "lively",
30
+ "merry",
31
+ "nice",
32
+ "proud",
33
+ "quick",
34
+ "silly",
35
+ "tidy",
36
+ "witty",
37
+ "zealous",
38
+ "bright",
39
+ "shy",
40
+ "bold",
41
+ "clever",
42
+ "daring",
43
+ "elegant",
44
+ "faithful",
45
+ "graceful",
46
+ "honest",
47
+ "inventive",
48
+ "jovial",
49
+ "keen",
50
+ "lucky",
51
+ "modest",
52
+ "noble",
53
+ "optimistic",
54
+ "patient",
55
+ "quirky",
56
+ "resourceful",
57
+ "sincere",
58
+ "thoughtful",
59
+ "upbeat",
60
+ "valiant",
61
+ "warm",
62
+ "youthful",
63
+ "zesty",
64
+ "adventurous",
65
+ "breezy",
66
+ "cheerful",
67
+ "delightful",
68
+ "energetic",
69
+ "fearless",
70
+ "glad",
71
+ "hopeful",
72
+ "imaginative",
73
+ "joyful",
74
+ "kindly",
75
+ "luminous",
76
+ "mysterious",
77
+ "neat",
78
+ "outgoing",
79
+ "playful",
80
+ "radiant",
81
+ "spirited",
82
+ "tranquil",
83
+ "unique",
84
+ "vivid",
85
+ "wise",
86
+ "zany",
87
+ "artful",
88
+ "bubbly",
89
+ "charming",
90
+ "dazzling",
91
+ "earnest",
92
+ "festive",
93
+ "gentlemanly",
94
+ "hearty",
95
+ "intrepid",
96
+ "jubilant",
97
+ "knightly",
98
+ "lively",
99
+ "magnetic",
100
+ "nimble",
101
+ "orderly",
102
+ "peaceful",
103
+ "quick-witted",
104
+ "robust",
105
+ "sturdy",
106
+ "trusty",
107
+ "upstanding",
108
+ "vibrant",
109
+ "whimsical",
110
+ ]
111
+ nouns = [
112
+ "sunset",
113
+ "forest",
114
+ "river",
115
+ "mountain",
116
+ "breeze",
117
+ "meadow",
118
+ "ocean",
119
+ "valley",
120
+ "sky",
121
+ "field",
122
+ "cloud",
123
+ "star",
124
+ "rain",
125
+ "leaf",
126
+ "stone",
127
+ "flower",
128
+ "bird",
129
+ "tree",
130
+ "wave",
131
+ "trail",
132
+ "island",
133
+ "desert",
134
+ "hill",
135
+ "lake",
136
+ "pond",
137
+ "grove",
138
+ "canyon",
139
+ "reef",
140
+ "bay",
141
+ "peak",
142
+ "glade",
143
+ "marsh",
144
+ "cliff",
145
+ "dune",
146
+ "spring",
147
+ "brook",
148
+ "cave",
149
+ "plain",
150
+ "ridge",
151
+ "wood",
152
+ "blossom",
153
+ "petal",
154
+ "root",
155
+ "branch",
156
+ "seed",
157
+ "acorn",
158
+ "pine",
159
+ "willow",
160
+ "cedar",
161
+ "elm",
162
+ "falcon",
163
+ "eagle",
164
+ "sparrow",
165
+ "robin",
166
+ "owl",
167
+ "finch",
168
+ "heron",
169
+ "crane",
170
+ "duck",
171
+ "swan",
172
+ "fox",
173
+ "wolf",
174
+ "bear",
175
+ "deer",
176
+ "moose",
177
+ "otter",
178
+ "beaver",
179
+ "lynx",
180
+ "hare",
181
+ "badger",
182
+ "butterfly",
183
+ "bee",
184
+ "ant",
185
+ "beetle",
186
+ "dragonfly",
187
+ "firefly",
188
+ "ladybug",
189
+ "moth",
190
+ "spider",
191
+ "worm",
192
+ "coral",
193
+ "kelp",
194
+ "shell",
195
+ "pebble",
196
+ "boulder",
197
+ "cobble",
198
+ "sand",
199
+ "wavelet",
200
+ "tide",
201
+ "current",
202
+ ]
203
+ adjective = random.choice(adjectives)
204
+ noun = random.choice(nouns)
205
+ number = random.randint(1, 99)
206
+ return f"{adjective}-{noun}-{number}"
207
+
208
+
209
+ def block_except_in_notebook():
210
+ in_notebook = bool(getattr(sys, "ps1", sys.flags.interactive))
211
+ if in_notebook:
212
+ return
213
+ try:
214
+ while True:
215
+ time.sleep(0.1)
216
+ except (KeyboardInterrupt, OSError):
217
+ print("Keyboard interruption in main thread... closing dashboard.")
version.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 0.0.10