tensorbored 2.21.0rc1769988778__py3-none-any.whl → 2.21.0rc1770021042__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,451 @@
1
+ # Copyright 2026 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Perceptually uniform color sampling for TensorBoard run colors.
16
+
17
+ This module provides utilities for generating visually distinguishable colors
18
+ using the OKLCH color space, which is perceptually uniform. This means equal
19
+ steps in the color space correspond to equal perceived differences.
20
+
21
+ Example usage:
22
+
23
+ from tensorbored.plugins.core import color_sampler
24
+
25
+ # Get 5 evenly-spaced colors
26
+ colors = color_sampler.sample_colors(5)
27
+ # ['#dc8a78', '#a4b93e', '#40c4aa', '#7aa6f5', '#d898d5']
28
+
29
+ # Use with run_colors
30
+ run_ids = ['train', 'eval', 'test', 'baseline', 'experiment']
31
+ run_colors = {rid: color_sampler.sample_colors(len(run_ids))[i]
32
+ for i, rid in enumerate(run_ids)}
33
+
34
+ # Or use the ColorMap class for cleaner syntax
35
+ cm = color_sampler.ColorMap(len(run_ids))
36
+ run_colors = {rid: cm(i) for i, rid in enumerate(run_ids)}
37
+
38
+ # Even simpler - auto-assign from list
39
+ run_colors = color_sampler.colors_for_runs(run_ids)
40
+ """
41
+
42
+ import math
43
+ from typing import List, Tuple
44
+
45
+ # =============================================================================
46
+ # OKLCH Color Space Implementation
47
+ # =============================================================================
48
+ # OKLCH is a perceptually uniform color space where:
49
+ # L = Lightness (0 = black, 1 = white)
50
+ # C = Chroma (0 = gray, higher = more saturated)
51
+ # H = Hue angle in degrees (0-360)
52
+ #
53
+ # We convert OKLCH → OKLAB → Linear sRGB → sRGB → Hex
54
+
55
+
56
+ def _oklch_to_oklab(L: float, C: float, H: float) -> Tuple[float, float, float]:
57
+ """Convert OKLCH to OKLAB."""
58
+ h_rad = math.radians(H)
59
+ a = C * math.cos(h_rad)
60
+ b = C * math.sin(h_rad)
61
+ return (L, a, b)
62
+
63
+
64
+ def _oklab_to_linear_srgb(
65
+ L: float, a: float, b: float
66
+ ) -> Tuple[float, float, float]:
67
+ """Convert OKLAB to linear sRGB."""
68
+ # OKLAB to LMS (approximate)
69
+ l_ = L + 0.3963377774 * a + 0.2158037573 * b
70
+ m_ = L - 0.1055613458 * a - 0.0638541728 * b
71
+ s_ = L - 0.0894841775 * a - 1.2914855480 * b
72
+
73
+ # Cube the values
74
+ l = l_ * l_ * l_
75
+ m = m_ * m_ * m_
76
+ s = s_ * s_ * s_
77
+
78
+ # LMS to linear sRGB
79
+ r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s
80
+ g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s
81
+ b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
82
+
83
+ return (r, g, b)
84
+
85
+
86
+ def _linear_to_srgb(x: float) -> float:
87
+ """Convert linear RGB component to sRGB (gamma correction)."""
88
+ if x <= 0.0031308:
89
+ return 12.92 * x
90
+ return 1.055 * (x ** (1 / 2.4)) - 0.055
91
+
92
+
93
+ def _clamp(x: float, lo: float = 0.0, hi: float = 1.0) -> float:
94
+ """Clamp value to range."""
95
+ return max(lo, min(hi, x))
96
+
97
+
98
+ def _oklch_to_hex(L: float, C: float, H: float) -> str:
99
+ """Convert OKLCH color to hex string."""
100
+ # OKLCH → OKLAB → Linear sRGB → sRGB
101
+ lab = _oklch_to_oklab(L, C, H)
102
+ linear = _oklab_to_linear_srgb(*lab)
103
+ srgb = tuple(_clamp(_linear_to_srgb(c)) for c in linear)
104
+
105
+ # Convert to 8-bit and format as hex
106
+ r = int(round(srgb[0] * 255))
107
+ g = int(round(srgb[1] * 255))
108
+ b = int(round(srgb[2] * 255))
109
+
110
+ return f"#{r:02x}{g:02x}{b:02x}"
111
+
112
+
113
+ # =============================================================================
114
+ # Public API
115
+ # =============================================================================
116
+
117
+
118
+ def sample_colors(
119
+ n: int,
120
+ lightness: float = 0.7,
121
+ chroma: float = 0.15,
122
+ hue_start: float = 0.0,
123
+ hue_range: float = 360.0,
124
+ ) -> List[str]:
125
+ """Generate n perceptually uniform, evenly-spaced colors.
126
+
127
+ Uses the OKLCH color space to ensure colors are visually distinguishable.
128
+ Colors are spaced evenly around the hue wheel while maintaining consistent
129
+ lightness and chroma for uniform appearance.
130
+
131
+ Args:
132
+ n: Number of colors to generate.
133
+ lightness: OKLCH lightness (0-1). Default 0.7 works well on white
134
+ backgrounds. Use ~0.65 for dark backgrounds.
135
+ chroma: OKLCH chroma (0-0.4). Higher = more saturated. Default 0.15
136
+ gives vivid but not garish colors.
137
+ hue_start: Starting hue angle in degrees (0-360). Shifts the color
138
+ palette around the wheel.
139
+ hue_range: Range of hues to use (default 360 = full wheel). Use less
140
+ to restrict to a portion of the spectrum.
141
+
142
+ Returns:
143
+ List of n hex color strings (e.g., ['#dc8a78', '#40c4aa', ...]).
144
+
145
+ Example:
146
+ >>> sample_colors(3)
147
+ ['#dc8a78', '#5fba72', '#7a9ef7']
148
+
149
+ >>> sample_colors(5, lightness=0.6, chroma=0.2)
150
+ ['#d96a5c', '#8ba600', '#00ab9e', '#4d95f2', '#c87ed4']
151
+ """
152
+ if n <= 0:
153
+ return []
154
+
155
+ colors = []
156
+ for i in range(n):
157
+ # Evenly space hues, leaving a gap so first and last aren't too close
158
+ hue = (hue_start + (i * hue_range / n)) % 360
159
+ colors.append(_oklch_to_hex(lightness, chroma, hue))
160
+
161
+ return colors
162
+
163
+
164
+ def sample_colors_varied(
165
+ n: int,
166
+ lightness_range: Tuple[float, float] = (0.55, 0.8),
167
+ chroma_range: Tuple[float, float] = (0.12, 0.18),
168
+ ) -> List[str]:
169
+ """Generate n colors with varied lightness and chroma for maximum distinction.
170
+
171
+ When you have many colors (>8), varying lightness and chroma in addition
172
+ to hue helps distinguish them. This function maximizes perceptual distance
173
+ between colors.
174
+
175
+ Args:
176
+ n: Number of colors to generate.
177
+ lightness_range: (min, max) lightness values.
178
+ chroma_range: (min, max) chroma values.
179
+
180
+ Returns:
181
+ List of n hex color strings optimized for visual distinction.
182
+
183
+ Example:
184
+ >>> sample_colors_varied(10) # Good for 10+ runs
185
+ """
186
+ if n <= 0:
187
+ return []
188
+
189
+ colors = []
190
+ l_min, l_max = lightness_range
191
+ c_min, c_max = chroma_range
192
+
193
+ for i in range(n):
194
+ # Primary variation: hue
195
+ hue = (i * 360 / n) % 360
196
+
197
+ # Secondary variation: alternate lightness and chroma
198
+ # This creates a "zigzag" pattern in L-C space
199
+ t = i / max(n - 1, 1)
200
+
201
+ if i % 2 == 0:
202
+ lightness = l_min + (l_max - l_min) * (1 - t * 0.5)
203
+ chroma = c_min + (c_max - c_min) * t
204
+ else:
205
+ lightness = l_min + (l_max - l_min) * (0.5 + t * 0.5)
206
+ chroma = c_max - (c_max - c_min) * t * 0.5
207
+
208
+ colors.append(_oklch_to_hex(lightness, chroma, hue))
209
+
210
+ return colors
211
+
212
+
213
+ class ColorMap:
214
+ """A callable color map that returns colors by index.
215
+
216
+ Convenient for use with enumerate() or dict comprehensions.
217
+
218
+ Example:
219
+ >>> cm = ColorMap(5)
220
+ >>> cm(0)
221
+ '#dc8a78'
222
+ >>> cm(2)
223
+ '#40c4aa'
224
+ >>> run_colors = {rid: cm(i) for i, rid in enumerate(run_ids)}
225
+ """
226
+
227
+ def __init__(
228
+ self,
229
+ n: int,
230
+ lightness: float = 0.7,
231
+ chroma: float = 0.15,
232
+ hue_start: float = 0.0,
233
+ varied: bool = False,
234
+ ):
235
+ """Create a color map with n colors.
236
+
237
+ Args:
238
+ n: Number of colors in the palette.
239
+ lightness: OKLCH lightness (ignored if varied=True).
240
+ chroma: OKLCH chroma (ignored if varied=True).
241
+ hue_start: Starting hue angle.
242
+ varied: If True, use sample_colors_varied() for better distinction
243
+ with many colors (>8).
244
+ """
245
+ if varied:
246
+ self._colors = sample_colors_varied(n)
247
+ else:
248
+ self._colors = sample_colors(n, lightness, chroma, hue_start)
249
+
250
+ def __call__(self, index: int) -> str:
251
+ """Get color at index (wraps around if out of bounds)."""
252
+ if not self._colors:
253
+ return "#808080" # Gray fallback
254
+ return self._colors[index % len(self._colors)]
255
+
256
+ def __len__(self) -> int:
257
+ return len(self._colors)
258
+
259
+ def __iter__(self):
260
+ return iter(self._colors)
261
+
262
+ def __getitem__(self, index: int) -> str:
263
+ return self._colors[index]
264
+
265
+
266
+ def colors_for_runs(
267
+ run_ids: List[str],
268
+ lightness: float = 0.7,
269
+ chroma: float = 0.15,
270
+ varied: bool = False,
271
+ ) -> dict:
272
+ """Generate a run_colors dict for a list of run IDs.
273
+
274
+ Convenience function that creates a complete run_colors mapping.
275
+
276
+ Args:
277
+ run_ids: List of run identifiers.
278
+ lightness: OKLCH lightness.
279
+ chroma: OKLCH chroma.
280
+ varied: Use varied lightness/chroma for many runs.
281
+
282
+ Returns:
283
+ Dict mapping run IDs to hex color strings.
284
+
285
+ Example:
286
+ >>> colors_for_runs(['train', 'eval', 'test'])
287
+ {'train': '#dc8a78', 'eval': '#5fba72', 'test': '#7a9ef7'}
288
+ """
289
+ n = len(run_ids)
290
+ if varied or n > 8:
291
+ colors = sample_colors_varied(n)
292
+ else:
293
+ colors = sample_colors(n, lightness, chroma)
294
+
295
+ return {rid: colors[i] for i, rid in enumerate(run_ids)}
296
+
297
+
298
+ # =============================================================================
299
+ # Preset Palettes
300
+ # =============================================================================
301
+
302
+
303
+ def palette_categorical(n: int) -> List[str]:
304
+ """Generate a categorical palette optimized for charts.
305
+
306
+ Uses high chroma and medium lightness for maximum pop on white backgrounds.
307
+ """
308
+ return sample_colors(n, lightness=0.65, chroma=0.18)
309
+
310
+
311
+ def palette_sequential(n: int, hue: float = 250) -> List[str]:
312
+ """Generate a sequential palette (light to dark) for ordered data.
313
+
314
+ All colors have the same hue but vary in lightness.
315
+
316
+ Args:
317
+ n: Number of colors.
318
+ hue: Base hue (default 250 = blue).
319
+
320
+ Returns:
321
+ List of colors from light to dark.
322
+ """
323
+ if n <= 0:
324
+ return []
325
+
326
+ colors = []
327
+ for i in range(n):
328
+ # Lightness from 0.9 (light) to 0.35 (dark)
329
+ lightness = 0.9 - (i / max(n - 1, 1)) * 0.55
330
+ # Chroma increases slightly with darkness
331
+ chroma = 0.08 + (i / max(n - 1, 1)) * 0.12
332
+ colors.append(_oklch_to_hex(lightness, chroma, hue))
333
+
334
+ return colors
335
+
336
+
337
+ def palette_diverging(
338
+ n: int, hue_low: float = 250, hue_high: float = 30
339
+ ) -> List[str]:
340
+ """Generate a diverging palette for data with a meaningful midpoint.
341
+
342
+ Goes from one hue through neutral to another hue.
343
+
344
+ Args:
345
+ n: Number of colors (odd numbers work best).
346
+ hue_low: Hue for low values (default 250 = blue).
347
+ hue_high: Hue for high values (default 30 = orange).
348
+
349
+ Returns:
350
+ List of colors diverging from center.
351
+ """
352
+ if n <= 0:
353
+ return []
354
+
355
+ colors = []
356
+ mid = (n - 1) / 2
357
+
358
+ for i in range(n):
359
+ if i < mid:
360
+ # Low side: blue-ish
361
+ t = i / mid if mid > 0 else 0
362
+ lightness = 0.45 + t * 0.45 # Dark to light
363
+ chroma = 0.18 * (1 - t) # Saturated to neutral
364
+ hue = hue_low
365
+ elif i > mid:
366
+ # High side: orange-ish
367
+ t = (i - mid) / (n - 1 - mid) if n - 1 > mid else 0
368
+ lightness = 0.9 - t * 0.45 # Light to dark
369
+ chroma = 0.18 * t # Neutral to saturated
370
+ hue = hue_high
371
+ else:
372
+ # Midpoint: neutral
373
+ lightness = 0.9
374
+ chroma = 0.0
375
+ hue = 0
376
+
377
+ colors.append(_oklch_to_hex(lightness, chroma, hue))
378
+
379
+ return colors
380
+
381
+
382
+ # =============================================================================
383
+ # Color Utilities
384
+ # =============================================================================
385
+
386
+
387
+ def lighten(hex_color: str, amount: float = 0.1) -> str:
388
+ """Lighten a hex color by increasing its OKLCH lightness.
389
+
390
+ Args:
391
+ hex_color: Input color as hex string (e.g., '#dc8a78').
392
+ amount: How much to lighten (0-1).
393
+
394
+ Returns:
395
+ Lightened hex color string.
396
+ """
397
+ l, c, h = _hex_to_oklch(hex_color)
398
+ l = min(1.0, l + amount)
399
+ return _oklch_to_hex(l, c, h)
400
+
401
+
402
+ def darken(hex_color: str, amount: float = 0.1) -> str:
403
+ """Darken a hex color by decreasing its OKLCH lightness.
404
+
405
+ Args:
406
+ hex_color: Input color as hex string.
407
+ amount: How much to darken (0-1).
408
+
409
+ Returns:
410
+ Darkened hex color string.
411
+ """
412
+ l, c, h = _hex_to_oklch(hex_color)
413
+ l = max(0.0, l - amount)
414
+ return _oklch_to_hex(l, c, h)
415
+
416
+
417
+ def _hex_to_oklch(hex_color: str) -> Tuple[float, float, float]:
418
+ """Convert hex color to OKLCH (approximate reverse conversion)."""
419
+ # Parse hex
420
+ hex_color = hex_color.lstrip("#")
421
+ r = int(hex_color[0:2], 16) / 255
422
+ g = int(hex_color[2:4], 16) / 255
423
+ b = int(hex_color[4:6], 16) / 255
424
+
425
+ # sRGB to linear
426
+ def to_linear(c):
427
+ return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
428
+
429
+ r_lin = to_linear(r)
430
+ g_lin = to_linear(g)
431
+ b_lin = to_linear(b)
432
+
433
+ # Linear sRGB to LMS
434
+ l = 0.4122214708 * r_lin + 0.5363325363 * g_lin + 0.0514459929 * b_lin
435
+ m = 0.2119034982 * r_lin + 0.6806995451 * g_lin + 0.1073969566 * b_lin
436
+ s = 0.0883024619 * r_lin + 0.2817188376 * g_lin + 0.6299787005 * b_lin
437
+
438
+ # LMS to OKLAB
439
+ l_ = l ** (1 / 3) if l >= 0 else -((-l) ** (1 / 3))
440
+ m_ = m ** (1 / 3) if m >= 0 else -((-m) ** (1 / 3))
441
+ s_ = s ** (1 / 3) if s >= 0 else -((-s) ** (1 / 3))
442
+
443
+ L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_
444
+ a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_
445
+ b_val = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
446
+
447
+ # OKLAB to OKLCH
448
+ C = math.sqrt(a * a + b_val * b_val)
449
+ H = math.degrees(math.atan2(b_val, a)) % 360
450
+
451
+ return (L, C, H)
@@ -0,0 +1,280 @@
1
+ # Copyright 2026 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Utility for writing TensorBoard default profiles from Python.
16
+
17
+ This module provides a simple API for training scripts to set default
18
+ TensorBoard dashboard configurations. When users load TensorBoard, the
19
+ default profile will be automatically applied.
20
+
21
+ Example usage:
22
+
23
+ from tensorbored.plugins.core import profile_writer
24
+
25
+ # Create a profile with pinned cards and run colors
26
+ profile = profile_writer.create_profile(
27
+ name="Training Dashboard",
28
+ pinned_cards=[
29
+ {"plugin": "scalars", "tag": "train/loss"},
30
+ {"plugin": "scalars", "tag": "train/accuracy"},
31
+ {"plugin": "scalars", "tag": "eval/loss"},
32
+ ],
33
+ run_colors={
34
+ "train": "#2196F3", # Blue
35
+ "eval": "#4CAF50", # Green
36
+ },
37
+ tag_filter="loss|accuracy",
38
+ smoothing=0.8,
39
+ )
40
+
41
+ # Write the profile to the logdir
42
+ profile_writer.write_profile(logdir, profile)
43
+
44
+ # Or use the convenience function
45
+ profile_writer.set_default_profile(
46
+ logdir,
47
+ pinned_cards=[{"plugin": "scalars", "tag": "train/loss"}],
48
+ run_colors={"train": "#ff0000"},
49
+ )
50
+ """
51
+
52
+ import json
53
+ import os
54
+ import time
55
+ from typing import Any, Dict, List, Optional
56
+
57
+ # Profile format version
58
+ PROFILE_VERSION = 1
59
+
60
+
61
+ def create_profile(
62
+ name: str = "Default Profile",
63
+ pinned_cards: Optional[List[Dict[str, Any]]] = None,
64
+ run_colors: Optional[Dict[str, str]] = None,
65
+ group_colors: Optional[List[Dict[str, Any]]] = None,
66
+ superimposed_cards: Optional[List[Dict[str, Any]]] = None,
67
+ tag_filter: str = "",
68
+ run_filter: str = "",
69
+ smoothing: float = 0.6,
70
+ group_by: Optional[Dict[str, Any]] = None,
71
+ ) -> Dict[str, Any]:
72
+ """Create a TensorBoard profile dictionary.
73
+
74
+ Args:
75
+ name: User-friendly name for the profile.
76
+ pinned_cards: List of cards to pin. Each card is a dict with:
77
+ - plugin: str (e.g., "scalars", "images", "histograms")
78
+ - tag: str (the tag name)
79
+ - runId: str (optional, for single-run plugins)
80
+ - sample: int (optional, for sampled plugins like images)
81
+ run_colors: Dict mapping run names/IDs to hex color strings
82
+ (e.g., {"run1": "#ff0000", "run2": "#00ff00"}).
83
+ group_colors: List of group color assignments. Each entry is a dict:
84
+ - groupKey: str
85
+ - colorId: int
86
+ superimposed_cards: List of superimposed card definitions. Each is:
87
+ - id: str (unique identifier)
88
+ - title: str (display title)
89
+ - tags: List[str] (scalar tags to combine)
90
+ - runId: Optional[str] (run filter, or None for all runs)
91
+ tag_filter: Regex pattern to filter tags.
92
+ run_filter: Regex pattern to filter runs.
93
+ smoothing: Scalar smoothing value (0.0 to 0.999).
94
+ group_by: Grouping configuration dict with:
95
+ - key: str ("RUN", "EXPERIMENT", "REGEX", or "REGEX_BY_EXP")
96
+ - regexString: str (optional, for REGEX/REGEX_BY_EXP)
97
+
98
+ Returns:
99
+ A profile dictionary ready to be written to the logdir.
100
+ """
101
+ # Convert run_colors dict to list format
102
+ run_color_entries = []
103
+ if run_colors:
104
+ for run_id, color in run_colors.items():
105
+ run_color_entries.append({"runId": run_id, "color": color})
106
+
107
+ return {
108
+ "version": PROFILE_VERSION,
109
+ "data": {
110
+ "version": PROFILE_VERSION,
111
+ "name": name,
112
+ "lastModifiedTimestamp": int(time.time() * 1000),
113
+ "pinnedCards": pinned_cards or [],
114
+ "runColors": run_color_entries,
115
+ "groupColors": group_colors or [],
116
+ "superimposedCards": superimposed_cards or [],
117
+ "tagFilter": tag_filter,
118
+ "runFilter": run_filter,
119
+ "smoothing": smoothing,
120
+ "groupBy": group_by,
121
+ },
122
+ }
123
+
124
+
125
+ def write_profile(logdir: str, profile: Dict[str, Any]) -> str:
126
+ """Write a profile to the logdir.
127
+
128
+ The profile will be written to `<logdir>/.tensorboard/default_profile.json`.
129
+ When TensorBoard starts with this logdir, it will offer this profile
130
+ as the default dashboard configuration.
131
+
132
+ Args:
133
+ logdir: The TensorBoard log directory.
134
+ profile: A profile dictionary (from create_profile or manually created).
135
+
136
+ Returns:
137
+ The path to the written profile file.
138
+
139
+ Raises:
140
+ ValueError: If the profile is missing required fields.
141
+ OSError: If unable to write to the logdir.
142
+ """
143
+ if "version" not in profile:
144
+ raise ValueError("Profile must have a 'version' field")
145
+
146
+ profile_dir = os.path.join(logdir, ".tensorboard")
147
+ os.makedirs(profile_dir, exist_ok=True)
148
+
149
+ profile_path = os.path.join(profile_dir, "default_profile.json")
150
+ with open(profile_path, "w", encoding="utf-8") as f:
151
+ json.dump(profile, f, indent=2)
152
+
153
+ return profile_path
154
+
155
+
156
+ def read_profile(logdir: str) -> Optional[Dict[str, Any]]:
157
+ """Read the default profile from a logdir.
158
+
159
+ Args:
160
+ logdir: The TensorBoard log directory.
161
+
162
+ Returns:
163
+ The profile dictionary, or None if no profile exists.
164
+ """
165
+ profile_path = os.path.join(logdir, ".tensorboard", "default_profile.json")
166
+ if not os.path.exists(profile_path):
167
+ return None
168
+
169
+ try:
170
+ with open(profile_path, "r", encoding="utf-8") as f:
171
+ return json.load(f)
172
+ except (json.JSONDecodeError, OSError):
173
+ return None
174
+
175
+
176
+ def set_default_profile(
177
+ logdir: str,
178
+ name: str = "Default Profile",
179
+ pinned_cards: Optional[List[Dict[str, Any]]] = None,
180
+ run_colors: Optional[Dict[str, str]] = None,
181
+ group_colors: Optional[List[Dict[str, Any]]] = None,
182
+ superimposed_cards: Optional[List[Dict[str, Any]]] = None,
183
+ tag_filter: str = "",
184
+ run_filter: str = "",
185
+ smoothing: float = 0.6,
186
+ group_by: Optional[Dict[str, Any]] = None,
187
+ ) -> str:
188
+ """Convenience function to create and write a profile in one call.
189
+
190
+ Args:
191
+ logdir: The TensorBoard log directory.
192
+ name: User-friendly name for the profile.
193
+ pinned_cards: List of cards to pin (see create_profile).
194
+ run_colors: Dict mapping run names to hex colors.
195
+ group_colors: List of group color assignments.
196
+ superimposed_cards: List of superimposed card definitions.
197
+ tag_filter: Regex pattern to filter tags.
198
+ run_filter: Regex pattern to filter runs.
199
+ smoothing: Scalar smoothing value.
200
+ group_by: Grouping configuration.
201
+
202
+ Returns:
203
+ The path to the written profile file.
204
+ """
205
+ profile = create_profile(
206
+ name=name,
207
+ pinned_cards=pinned_cards,
208
+ run_colors=run_colors,
209
+ group_colors=group_colors,
210
+ superimposed_cards=superimposed_cards,
211
+ tag_filter=tag_filter,
212
+ run_filter=run_filter,
213
+ smoothing=smoothing,
214
+ group_by=group_by,
215
+ )
216
+ return write_profile(logdir, profile)
217
+
218
+
219
+ def pin_scalar(tag: str) -> Dict[str, str]:
220
+ """Helper to create a pinned scalar card entry.
221
+
222
+ Args:
223
+ tag: The scalar tag name (e.g., "train/loss").
224
+
225
+ Returns:
226
+ A dict suitable for the pinned_cards list.
227
+ """
228
+ return {"plugin": "scalars", "tag": tag}
229
+
230
+
231
+ def pin_histogram(tag: str, run_id: str) -> Dict[str, str]:
232
+ """Helper to create a pinned histogram card entry.
233
+
234
+ Args:
235
+ tag: The histogram tag name.
236
+ run_id: The run ID (required for histograms).
237
+
238
+ Returns:
239
+ A dict suitable for the pinned_cards list.
240
+ """
241
+ return {"plugin": "histograms", "tag": tag, "runId": run_id}
242
+
243
+
244
+ def pin_image(tag: str, run_id: str, sample: int = 0) -> Dict[str, Any]:
245
+ """Helper to create a pinned image card entry.
246
+
247
+ Args:
248
+ tag: The image tag name.
249
+ run_id: The run ID (required for images).
250
+ sample: The sample index (default 0).
251
+
252
+ Returns:
253
+ A dict suitable for the pinned_cards list.
254
+ """
255
+ return {"plugin": "images", "tag": tag, "runId": run_id, "sample": sample}
256
+
257
+
258
+ def create_superimposed_card(
259
+ title: str,
260
+ tags: List[str],
261
+ run_id: Optional[str] = None,
262
+ ) -> Dict[str, Any]:
263
+ """Helper to create a superimposed card entry.
264
+
265
+ Superimposed cards combine multiple scalar tags on a single plot.
266
+
267
+ Args:
268
+ title: Display title for the card.
269
+ tags: List of scalar tag names to superimpose.
270
+ run_id: Optional run ID filter (None shows all runs).
271
+
272
+ Returns:
273
+ A dict suitable for the superimposed_cards list.
274
+ """
275
+ return {
276
+ "id": f"superimposed-{int(time.time() * 1000)}",
277
+ "title": title,
278
+ "tags": tags,
279
+ "runId": run_id,
280
+ }
tensorbored/version.py CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  """Contains the version string."""
17
17
 
18
- VERSION = "2.21.0rc1769988778"
18
+ VERSION = "2.21.0rc1770021042"
19
19
 
20
20
  if __name__ == "__main__":
21
21
  print(VERSION)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorbored
3
- Version: 2.21.0rc1769988778
3
+ Version: 2.21.0rc1770021042
4
4
  Summary: TensorBored: a PyTorch-first TensorBoard fork
5
5
  Home-page: https://github.com/Demonstrandum/tensorbored
6
6
  Author: TensorBored authors
@@ -13,7 +13,7 @@ tensorbored/manager.py,sha256=6LKijMpiKUtPTpApEJnQby113MXM55-G_guhulGQsdA,16300
13
13
  tensorbored/notebook.py,sha256=zO8ruL979Lc1a6mGOW0J-o97RjbmIZ2nWci3l0y1wLA,14698
14
14
  tensorbored/plugin_util.py,sha256=DLJzEy9_50Iq-o14JGz1Q0DwaND9b64Q7BmDROyqZyQ,8376
15
15
  tensorbored/program.py,sha256=GHDG5FqtdJtAknzQekWGTYFXwpEnHXWHlAZoidN8Kmo,35500
16
- tensorbored/version.py,sha256=AfdPS8CCRCEMMkKsur2zm3EfK7yW98uP6i0ovMKMe0c,804
16
+ tensorbored/version.py,sha256=gIqOTSQDg2IAuXrWCI90-ktmVw68maIXrNjnEo1zqBQ,804
17
17
  tensorbored/webfiles.zip,sha256=1bIiTAQHXahMebNZdnQdctde_hd9Ws8oF69o-dFT_1g,4999505
18
18
  tensorbored/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  tensorbored/_vendor/bleach/__init__.py,sha256=T_iQHpeDCQpz55IVNEsktpOzw3QHvwj7rfTj34bWWKc,3689
@@ -156,7 +156,9 @@ tensorbored/plugins/audio/plugin_data_pb2.py,sha256=YwUPbBSQhslWWQKbUINEX8PSitC5
156
156
  tensorbored/plugins/audio/summary.py,sha256=w1xrRP-GFXWzgrY1MS9xxKWiAD0tBe8xYWZZ7dXM7A8,8572
157
157
  tensorbored/plugins/audio/summary_v2.py,sha256=LxtBrrU3qvHjmD7UjylFTIZHhcaSNrkdhzL9LaZX3oY,5453
158
158
  tensorbored/plugins/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
159
+ tensorbored/plugins/core/color_sampler.py,sha256=5xq5IFr9-n3azp044qAH4QwdLUiS0feuZT9puBZUDak,14035
159
160
  tensorbored/plugins/core/core_plugin.py,sha256=ID72vqZrJ--Ww8MIH4jHmJdWFQt1lxC7QAyQbKHG9Ag,35920
161
+ tensorbored/plugins/core/profile_writer.py,sha256=y4x8D1nzsFxwgizD9exjsv-RB2RSulMJ3rwVtUx6-AE,9320
160
162
  tensorbored/plugins/custom_scalar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
161
163
  tensorbored/plugins/custom_scalar/custom_scalars_plugin.py,sha256=vMJS3hBPBXxpd5zw4-LS1HxkHgB12-18cHZf3TXKmuM,11753
162
164
  tensorbored/plugins/custom_scalar/layout_pb2.py,sha256=9VfNLBWhLxEQvoOS-r0hgc9VabMBMr4hPiy1oVqwJgE,4340
@@ -263,9 +265,9 @@ tensorbored/util/platform_util.py,sha256=5jr42kvi6GqRzFx6YponSDOFYg7_RL-DTlk6dDe
263
265
  tensorbored/util/tb_logging.py,sha256=LNfFN1qZqAoZd6OaBWE_L_bqOqA_i1m9xj8lIZGQ_G4,780
264
266
  tensorbored/util/tensor_util.py,sha256=KfR_OMxxUjb8mp7yUH-x8CnEd0C8yxwHZ-vBQYDrdyE,21752
265
267
  tensorbored/util/timing.py,sha256=DduPKf8izD47D-UJ6D3xlcLDqI5WTAXK2d3WDmb3Abg,3734
266
- tensorbored-2.21.0rc1769988778.dist-info/licenses/LICENSE,sha256=1Y0t0nxgtnadUpTCqZlfVLWaaCju6MmbbODuxXOy-Rg,37111
267
- tensorbored-2.21.0rc1769988778.dist-info/METADATA,sha256=BzJ6uUMYOweHhjxhQ_s8SIjriQ7tllnnUZobSt7cGIY,1781
268
- tensorbored-2.21.0rc1769988778.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
269
- tensorbored-2.21.0rc1769988778.dist-info/entry_points.txt,sha256=WiMjmdrTDkZXBlZmKWt1Kz0FCymATWUZjyTHEW-v3LU,196
270
- tensorbored-2.21.0rc1769988778.dist-info/top_level.txt,sha256=F6oc1TBDSM0Hq_5vyIWijSnNewbXEBnBMS15t2S1jaY,12
271
- tensorbored-2.21.0rc1769988778.dist-info/RECORD,,
268
+ tensorbored-2.21.0rc1770021042.dist-info/licenses/LICENSE,sha256=1Y0t0nxgtnadUpTCqZlfVLWaaCju6MmbbODuxXOy-Rg,37111
269
+ tensorbored-2.21.0rc1770021042.dist-info/METADATA,sha256=fqNWOS3n1BB93t6wk1ENAR-EniFsdgEFR8c7gB6YnGg,1781
270
+ tensorbored-2.21.0rc1770021042.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
271
+ tensorbored-2.21.0rc1770021042.dist-info/entry_points.txt,sha256=WiMjmdrTDkZXBlZmKWt1Kz0FCymATWUZjyTHEW-v3LU,196
272
+ tensorbored-2.21.0rc1770021042.dist-info/top_level.txt,sha256=F6oc1TBDSM0Hq_5vyIWijSnNewbXEBnBMS15t2S1jaY,12
273
+ tensorbored-2.21.0rc1770021042.dist-info/RECORD,,