robo-goggles 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of robo-goggles might be problematic. Click here for more details.

Files changed (29) hide show
  1. robo_goggles-0.1.7/LICENSE +21 -0
  2. robo_goggles-0.1.7/PKG-INFO +601 -0
  3. robo_goggles-0.1.7/README.md +552 -0
  4. robo_goggles-0.1.7/goggles/__init__.py +859 -0
  5. robo_goggles-0.1.7/goggles/_core/decorators.py +103 -0
  6. robo_goggles-0.1.7/goggles/_core/integrations/__init__.py +29 -0
  7. robo_goggles-0.1.7/goggles/_core/integrations/console.py +125 -0
  8. robo_goggles-0.1.7/goggles/_core/integrations/storage.py +385 -0
  9. robo_goggles-0.1.7/goggles/_core/integrations/wandb.py +314 -0
  10. robo_goggles-0.1.7/goggles/_core/logger.py +606 -0
  11. robo_goggles-0.1.7/goggles/_core/routing.py +126 -0
  12. robo_goggles-0.1.7/goggles/config.py +68 -0
  13. robo_goggles-0.1.7/goggles/history/__init__.py +39 -0
  14. robo_goggles-0.1.7/goggles/history/buffer.py +186 -0
  15. robo_goggles-0.1.7/goggles/history/spec.py +144 -0
  16. robo_goggles-0.1.7/goggles/history/types.py +9 -0
  17. robo_goggles-0.1.7/goggles/history/utils.py +191 -0
  18. robo_goggles-0.1.7/goggles/media.py +284 -0
  19. robo_goggles-0.1.7/goggles/shutdown.py +69 -0
  20. robo_goggles-0.1.7/goggles/types.py +81 -0
  21. robo_goggles-0.1.7/goggles/validation.py +35 -0
  22. robo_goggles-0.1.7/pyproject.toml +180 -0
  23. robo_goggles-0.1.7/robo_goggles.egg-info/PKG-INFO +601 -0
  24. robo_goggles-0.1.7/robo_goggles.egg-info/SOURCES.txt +27 -0
  25. robo_goggles-0.1.7/robo_goggles.egg-info/dependency_links.txt +1 -0
  26. robo_goggles-0.1.7/robo_goggles.egg-info/requires.txt +25 -0
  27. robo_goggles-0.1.7/robo_goggles.egg-info/top_level.txt +1 -0
  28. robo_goggles-0.1.7/setup.cfg +4 -0
  29. robo_goggles-0.1.7/tests/test_api.py +211 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Antonio Terpin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,601 @@
1
+ Metadata-Version: 2.4
2
+ Name: robo-goggles
3
+ Version: 0.1.7
4
+ Summary: Observability framework for robotics research
5
+ Author-email: Antonio Terpin <aterpin@ethz.ch>, Francesco Banelli <fbanelli@ethz.ch>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/antonioterpin/goggles
8
+ Project-URL: Repository, https://github.com/antonioterpin/goggles
9
+ Project-URL: Documentation, https://github.com/antonioterpin/goggles#readme
10
+ Project-URL: Issues, https://github.com/antonioterpin/goggles/issues
11
+ Keywords: robotics,logging,observability,jax,wandb,experiment-tracking
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: System :: Logging
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: ruamel.yaml
27
+ Requires-Dist: rich
28
+ Requires-Dist: portal>=3.7.3
29
+ Requires-Dist: typing-extensions>=4.15.0
30
+ Requires-Dist: netifaces>=0.11.0
31
+ Requires-Dist: pyyaml>=6.0.3
32
+ Requires-Dist: numpy>=1.23
33
+ Requires-Dist: imageio>=2.37.0
34
+ Requires-Dist: matplotlib>=3.10.7
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
38
+ Requires-Dist: hypothesis>=6.0.0; extra == "dev"
39
+ Requires-Dist: snowballstemmer==2.2.0; extra == "dev"
40
+ Requires-Dist: pre_commit==4.0.1; extra == "dev"
41
+ Requires-Dist: tomli; extra == "dev"
42
+ Requires-Dist: pyupgrade>=3.19.0; extra == "dev"
43
+ Provides-Extra: jax
44
+ Requires-Dist: jax>=0.4.0; extra == "jax"
45
+ Requires-Dist: jaxlib>=0.4.0; extra == "jax"
46
+ Provides-Extra: wandb
47
+ Requires-Dist: wandb[media]; extra == "wandb"
48
+ Dynamic: license-file
49
+
50
+ # 😎 Goggles - Observability for Robotics Research
51
+
52
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
53
+ [![GitHub stars](https://img.shields.io/github/stars/antonioterpin/robostack?style=social)](https://github.com/antonioterpin/goggles/stargazers)
54
+ [![codecov](https://codecov.io/gh/antonioterpin/goggles/graph/badge.svg?token=J49B8TFDSM)](https://codecov.io/gh/antonioterpin/goggles)
55
+ [![Tests](https://github.com/antonioterpin/goggles/actions/workflows/test.yaml/badge.svg)](https://github.com/antonioterpin/goggles/actions/workflows/test.yaml)
56
+ [![Code Style](https://github.com/antonioterpin/goggles/actions/workflows/code-style.yaml/badge.svg)](https://github.com/antonioterpin/goggles/actions/workflows/code-style.yaml)
57
+ [![PyPI version](https://img.shields.io/pypi/v/robo-goggles.svg)](https://pypi.org/project/robo-goggles)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
60
+
61
+
62
+ A lightweight, flexible Python observability framework designed for robotics research. Goggles provides structured logging, experiment tracking, performance profiling, and device-resident temporal memory management for JAX-based pipelines.
63
+
64
+ ## ✨ Features
65
+
66
+ - πŸ€– **Multi-process (and multi-machines) logging** - Synchronize logs across spawned processes reliably and efficiently (shared memory when available).
67
+ - 🎯 **Multi-output support** - Log to console, files, and remote services simultaneously.
68
+ - πŸ“Š **Experiment tracking** - Native integration with Weights & Biases for metrics, images, and videos.
69
+ - πŸ•’ **Performance profiling** - `@goggles.timeit` decorator for automatic runtime measurement.
70
+ - 🐞 **Error tracing** - `@goggles.trace_on_error` auto-logs full stack traces on exceptions.
71
+ - 🧠 **Device-resident histories** - JAX-based GPU memory management for efficient, long-running experiments metrics.
72
+ - 🚦 **Graceful shutdown** - Automatic cleanup of resources and handlers.
73
+ - βš™οΈ **Structured configuration** - YAML-based config loading with validation.
74
+ - πŸ”Œ **Extensible handlers** - Plugin architecture for custom logging backends.
75
+
76
+ ## πŸ—οΈ Projects Built with Goggles
77
+
78
+ This framework has been battle-tested across multiple research projects:
79
+
80
+ [![FluidSControl](https://img.shields.io/badge/GitHub-antonioterpin%2Ffluidscontrol-2ea44f?logo=github)](https://github.com/antonioterpin/fluidscontrol)
81
+ [![FlowGym](https://img.shields.io/badge/GitHub-antonioterpin%2Fflowgym-2ea44f?logo=github)](https://github.com/antonioterpin/flowgym)
82
+ [![SynthPix](https://img.shields.io/badge/GitHub-antonioterpin%2Fsynthpix-2ea44f?logo=github)](https://github.com/antonioterpin/synthpix)
83
+ [![Ξ net](https://img.shields.io/badge/GitHub-antonioterpin%2Fpinet-2ea44f?logo=github)](https://github.com/antonioterpin/pinet)
84
+ [![Glitch](https://img.shields.io/badge/GitHub-antonioterpin%2Fglitch-2ea44f?logo=github)](https://github.com/antonioterpin/glitch)
85
+
86
+ ## πŸš€ Quick Start
87
+
88
+ ### Installation
89
+
90
+ ```bash
91
+ # Basic installation
92
+ uv add robo-goggles # or pip install robo-goggles
93
+
94
+ # With Weights & Biases support
95
+ uv add "robo-goggles[wandb]"
96
+
97
+ # With JAX device-resident histories
98
+ uv add "robo-goggles[jax]"
99
+ ```
100
+
101
+ For the development installation, see our [How to contribute](./CONTRIBUTING.md) page.
102
+
103
+ ### Basic usage
104
+
105
+ ```python
106
+ import goggles as gg
107
+ import logging
108
+
109
+ # Set up console logging
110
+ logger = gg.get_logger("my_experiment")
111
+ gg.attach(
112
+ gg.ConsoleHandler(name="console", level=logging.INFO),
113
+ )
114
+
115
+ # Basic logging
116
+ logger.info("Experiment started")
117
+ logger.warning("This is a warning")
118
+ logger.error("An error occurred")
119
+ ```
120
+
121
+ See also [Example 1](./examples/01_basic_run.py), which you can run after cloning the repo with
122
+ ```bash
123
+ uv run examples/01_basic_run.py
124
+ ```
125
+
126
+ ### Experiment tracking with W&B
127
+
128
+ ```python
129
+ import goggles as gg
130
+ import numpy as np
131
+
132
+ # Enable metrics logging
133
+ logger = gg.get_logger("experiment", with_metrics=True)
134
+ gg.attach(
135
+ gg.WandBHandler(project="my_project", name="run_1"),
136
+ )
137
+
138
+ # Log metrics, images, and videos
139
+ for step in range(100):
140
+ logger.scalar("loss", np.random.random(), step=step)
141
+ logger.scalar("accuracy", 0.8 + 0.2 * np.random.random(), step=step)
142
+
143
+ # Log images and videos
144
+ image = np.random.randint(0, 255, (64, 64, 3), dtype=np.uint8)
145
+ logger.image(image, name="sample_image")
146
+
147
+ video = np.random.randint(0, 255, (30, 3, 64, 64), dtype=np.uint8)
148
+ logger.video(video, name="sample_video", fps=10)
149
+
150
+ # Ensure proper cleanup
151
+ gg.finish()
152
+ ```
153
+
154
+ ### Performance profiling and error tracking
155
+
156
+ ```python
157
+ import goggles as gg
158
+ import logging
159
+
160
+ class Trainer:
161
+ @gg.timeit(severity=logging.INFO)
162
+ def train_step(self, batch):
163
+ # Your training logic here
164
+ return {"loss": 0.1}
165
+
166
+ @gg.trace_on_error()
167
+ def risky_operation(self, data):
168
+ # This will log full traceback on any exception
169
+ return data / 0 # Will trigger trace logging
170
+
171
+ trainer = Trainer()
172
+ trainer.train_step({"x": [1, 2, 3]}) # Logs execution time
173
+
174
+ try:
175
+ trainer.risky_operation(10)
176
+ except ZeroDivisionError:
177
+ pass # Full traceback was automatically logged
178
+ ```
179
+
180
+ ### Configuration Management
181
+
182
+ Load and validate YAML configurations:
183
+
184
+ ```python
185
+ import goggles
186
+
187
+ # Load configuration with automatic validation
188
+ config = goggles.load_configuration("config.yaml")
189
+ print(config) # Pretty print
190
+ print(config["learning_rate"]) # Access as dict
191
+
192
+ # Pretty-print configuration
193
+ goggles.save_configuration(config, "output.yaml")
194
+ ```
195
+
196
+ ### Supported Platforms πŸ’»
197
+
198
+ | Platform | Basic | W&B | JAX/GPU | Development |
199
+ |----------|-------|-----|---------|-------------|
200
+ | Linux | βœ… | βœ… | βœ… | βœ… |
201
+ | macOS | βœ… | βœ… | βœ… | βœ… |
202
+ | Windows | βœ… | βœ… | ❌ | βœ… |
203
+
204
+ *GPU support requires CUDA-compatible hardware and drivers*
205
+
206
+ ## πŸ”₯ Examples
207
+
208
+ Explore the `examples/` directory for comprehensive usage patterns:
209
+
210
+ ```bash
211
+ # Basic logging setup
212
+ uv run examples/01_basic_run.py
213
+
214
+ # Advanced: Multi-scope logging
215
+ uv run examples/02_multi_scope.py
216
+
217
+ # File-based logging (local storage)
218
+ uv run examples/03_local_storage.py
219
+
220
+ # Weights & Biases integration
221
+ uv run examples/04_wandb.py
222
+
223
+ # Advanced: Weights & Biases multi-run setup
224
+ uv run examples/05_wandb_multiple_runs.py
225
+
226
+ # Advanced: Custom handler
227
+ uv run exacmples/06_custom_handler.py
228
+
229
+ # Graceful shutdown utils
230
+ uv run examples/100_interrupt.py
231
+
232
+ # Pretty and convenient utils for configuration laoding
233
+ uv run examples/101_config.py
234
+
235
+ # Advanced: Performance decorators
236
+ uv run examples/102_decorators.py
237
+
238
+ # Advanced: JAX device-resident histories
239
+ uv run examples/103_history.py
240
+ ```
241
+
242
+ ## 🧠 For Goggles power user
243
+
244
+ This section includes some cool functionalities of `goggles`. Enjoy!
245
+
246
+ ### Multi-scope logging
247
+ Goggles allow easily to set up different handlers for different scopes. That is, one can have an handler attached to multiple scopes, and a scope having multiple handlers. Each logger is associated to a single scope (by default: `global`), and logging with that logger will invoke all the loggers associated with the scope.
248
+
249
+ #### Why?
250
+ Within the same run, we may have logs that belong to different scopes. An example is training in Reinforcement Learning, where in a single training run there are multiple episodes. A complete example for this is provided in the [multiple runs in WandB](#multiple-runs-in-wandb) section.
251
+
252
+ #### Usage
253
+
254
+ ```python
255
+ # In this example, we set up a handlers associated
256
+ # to different scopes.
257
+ handler1 = gg.ConsoleHandler(name="examples.basic.console.1", level=logging.INFO)
258
+ gg.attach(handler1, scopes=["global", "scope1"])
259
+
260
+ handler2 = gg.ConsoleHandler(name="examples.basic.console.2", level=logging.INFO)
261
+ gg.attach(handler2, scopes=["global", "scope2"])
262
+
263
+ # We need to get separate loggers for each scope
264
+ logger_scope1 = gg.get_logger("examples.basic.scope1", scope="scope1")
265
+ logger_scope2 = gg.get_logger("examples.basic.scope2")
266
+ logger_scope2.bind(scope="scope2") # You can also bind the scope after creation
267
+ logger_global = gg.get_logger("examples.basic.global", scope="global")
268
+
269
+ # Now we can log messages to different scopes, so that only the interested
270
+ # handlers will process them.
271
+ logger_scope1.info(f"This will be logged only by {handler1.name}")
272
+ logger_scope2.info(f"This will be logged only by {handler2.name}")
273
+ logger_global.info("This will be logged by both handlers.")
274
+ ```
275
+
276
+ See also [examples/02_multi_scope.py](./examples/02_multi_scope.py) for a running example.
277
+
278
+ ### Multiple runs in WandB
279
+ An example of the benefit of scopes is given by the WandBHandler, which instantiate a different WandB run for each scope and groups them together:
280
+
281
+ ```python
282
+ import goggles as gg
283
+ from goggles import WandBHandler
284
+
285
+ # In this example, we set up multiple runs in Weights & Biases (W&B).
286
+ # All runs created by the handler will be grouped under
287
+ # the same project and group.
288
+ logger: gg.GogglesLogger = gg.get_logger("examples.basic", with_metrics=True)
289
+ handler = WandBHandler(
290
+ project="goggles_example", reinit="create_new", group="multiple_runs"
291
+ )
292
+
293
+ # In particular, we set up multiple runs in an RL training loop, with each
294
+ # episode being a separate W&B run and a global run tracking all episodes.
295
+ num_episodes = 3
296
+ episode_length = 10
297
+ scopes = [f"episode_{episode}" for episode in range(num_episodes + 1)]
298
+ scopes.append("global")
299
+ gg.attach(handler, scopes=scopes)
300
+
301
+
302
+ def my_episode(index: int):
303
+ episode_logger = gg.get_logger(scope=f"episode_{index}", with_metrics=True)
304
+ for step in range(episode_length):
305
+ # Supports scopes transparently
306
+ # and has its own step counter
307
+ episode_logger.scalar("env/reward", index * episode_length + step, step=step)
308
+
309
+
310
+ for i in range(num_episodes):
311
+ my_episode(i)
312
+ logger.scalar("total_reward", i, step=i)
313
+
314
+ # When using asynchronous logging (like wandb), make sure to finish
315
+ gg.finish()
316
+ ```
317
+
318
+ ### Fully asynchronous logging
319
+ As in the WandB example, all the handlers work in the background. By default, the logging calls are blocking, but can be made not blocking by setting the environment variable `GOGGLES_ASYNC` to `1` or `true`. When you use the async mode, remember to call `gg.finish()` at the end from your host machine!
320
+ >[!WARNING]
321
+ > This functionality still needs thorough tesing, as well as a better documentation. Help is appreciated! πŸ€—
322
+
323
+ ### Multi-machine logging
324
+ Goggles provides options to synchronize logging across machines, since there is always only a single server active. The relevant environment variables here are `GOGGLES_HOST` and `GOGGLES_PORT`.
325
+ >[!WARNING]
326
+ > This functionality still needs thorough tesing, as well as a better documentation. Help is appreciated! πŸ€—
327
+
328
+ ### Adding a custom handler
329
+ > [!NOTE]
330
+ > Ideally, you should open a PR: We would love to integrate your work!
331
+
332
+ Adding a custom handler is straightforward:
333
+
334
+ ```python
335
+ import goggles as gg
336
+ import logging
337
+
338
+
339
+ class CustomConsoleHandler(gg.ConsoleHandler):
340
+ """A custom console handler that adds a prefix to each log message."""
341
+
342
+ def handle(self, event: gg.Event) -> None:
343
+ dict = event.to_dict()
344
+
345
+ dict["payload"] = f"[CUSTOM PREFIX] {dict['payload']}"
346
+
347
+ event = gg.Event.from_dict(dict)
348
+ super().handle(event)
349
+
350
+
351
+ # Register the custom handler so it can be serialized/deserialized
352
+ gg.register_handler(CustomConsoleHandler)
353
+
354
+ # In this basic example, we set up a logger that outputs to the console.
355
+ logger = gg.get_logger("examples.custom_handler")
356
+
357
+
358
+ gg.attach(
359
+ CustomConsoleHandler(name="examples.custom.console", level=logging.INFO),
360
+ scopes=["global"],
361
+ )
362
+ # Because the logging level is set to INFO, the debug message will not be shown.
363
+ logger.info("Hello, world!")
364
+ logger.debug("you won't see this at INFO")
365
+
366
+ ```
367
+
368
+ See also [examples/05_custom_handler.py](./examples/06_custom_handler.py) for a complete example.
369
+
370
+ ### Device-resident histories
371
+ For long-running GPU experiments that need efficient temporal memory management:
372
+
373
+ #### Why?
374
+
375
+ During development of fluid control experiments and reinforcement learning pipelines, we needed to:
376
+ - Track detailed metrics during GPU-accelerated training
377
+ - Avoid expensive device-to-host transfers
378
+ - Maintain temporal state across episodes
379
+ - Support JIT compilation for maximum performance
380
+
381
+ #### Features
382
+
383
+ - **Pure functional** and **JIT-safe** buffer updates
384
+ - **Per-field history lengths** with episodic reset support
385
+ - **Batch-first convention**: `(B, T, *shape)` for all tensors
386
+ - **Zero host-device synchronization** during updates
387
+ - **Integrated with FlowGym's** `EstimatorState` for temporal RL memory
388
+
389
+ #### Usage
390
+
391
+ ```python
392
+ from goggles.history import HistorySpec, create_history, update_history
393
+ import jax.numpy as jnp
394
+
395
+ # Define what to track over time
396
+ spec = HistorySpec.from_config({
397
+ "states": {"length": 100, "shape": (64, 64, 2), "dtype": jnp.float32},
398
+ "actions": {"length": 50, "shape": (8,), "dtype": jnp.float32},
399
+ "rewards": {"length": 100, "shape": (), "dtype": jnp.float32},
400
+ })
401
+
402
+ # Create GPU-resident history buffers
403
+ history = create_history(spec, batch_size=32)
404
+ print(history["states"].shape) # (32, 100, 64, 64, 2)
405
+
406
+ # Update buffers during training (JIT-compiled)
407
+ new_state = jnp.ones((32, 64, 64, 2))
408
+ history = update_history(history, {"states": new_state})
409
+ ```
410
+
411
+ See also [examples/103_history.py](./examples/103_history.py) for a running example.
412
+
413
+
414
+ ## 🀝 Contributing
415
+
416
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for detailed information on:
417
+
418
+ β€’ Development workflow and environment setup
419
+ β€’ Code style requirements and automated checks
420
+ β€’ Testing standards and coverage expectations
421
+ β€’ PR preparation and commit message conventions
422
+
423
+ ## πŸ“„ License
424
+
425
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
426
+
427
+ ---
428
+
429
+ *Ready to enhance your robotics research with structured observability? Get started with Goggles today! πŸš€*gles
430
+
431
+ A lightweight, flexible Python logging and monitoring library designed to simplify and enhance experiment tracking, performance profiling, and error tracing. Integrates with terminal, file-based logs, and W\&B (Weights & Biases). It is thought primarily for research projects in robotics.
432
+
433
+ ```bash
434
+ pip install "goggles @ git+ssh://git@github.com/antonioterpin/goggles.git"
435
+ ```
436
+
437
+ ## Features
438
+
439
+ - πŸ€– **Multi-process, single-thread compatible**
440
+ Synchronize logs from all spawned processes via shared memory.
441
+
442
+ - 🎯 **Multi-output logging**
443
+ Log to terminal and/or file.
444
+
445
+ - πŸ•’ **Performance profiling**
446
+ `@goggles.timeit` decorator measures and logs runtime.
447
+
448
+ - 🐞 **Error tracing**
449
+ `@goggles.trace_on_error` auto-logs full stack on exceptions.
450
+
451
+ - πŸ“Š **Metrics tracking**
452
+ `goggles.scalar`, `goggles.vector`, `goggles.image`, `goggles.video` β†’ Weights & Biases.
453
+
454
+ - 🚦 **Graceful shutdown**
455
+ Call `goggles.cleanup()` (or hook into your own `signal` handler).
456
+
457
+ - βš™οΈ **Asynchronous scheduling**
458
+ Offload heavy logging tasks via `goggles.schedule_log(...)`.
459
+
460
+ - πŸ“ **Pretty configuration loading**
461
+ `goggles.load_configuration(...)` loads YAML with validation.
462
+
463
+ ## Quickstart
464
+
465
+ 1. TODO: update
466
+
467
+ 2. **Ready to log**:
468
+
469
+ ```python
470
+ import goggles
471
+
472
+ goggles.debug("Debugging details…")
473
+ goggles.info("Experiment started")
474
+ goggles.warning("This is a warning")
475
+ goggles.error("An error occurred")
476
+ ```
477
+
478
+ We cleanup all the resources automatically at exit.
479
+
480
+ ## Configuration
481
+
482
+ Pretty logging of configuration files.
483
+
484
+ ```python
485
+ import goggles
486
+
487
+ # Load from examples/example_config.yaml
488
+ config = goggles.load_configuration("examples/example_config.yaml")
489
+ print(config)
490
+
491
+ # Access as dict
492
+ print(f"time_per_experiment = {config['time_per_experiment']}")
493
+ ```
494
+
495
+ ## Decorators: `@goggles.timeit` and `@goggles.trace_on_error`
496
+
497
+ Measure execution time of methods or functions:
498
+
499
+ ```python
500
+ import goggles
501
+
502
+ class Worker:
503
+ @goggles.timeit(severity=logging.DEBUG)
504
+ def compute_heavy(self, n):
505
+ return sum(range(n))
506
+
507
+ @goggles.trace_on_error()
508
+ def risky_division(self, x, y):
509
+ return x / y
510
+
511
+ g = Worker()
512
+ g.compute_heavy(1_000_000)
513
+
514
+ try:
515
+ g.risky_division(1, 0)
516
+ except ZeroDivisionError:
517
+ pass # Full traceback was logged
518
+ ```
519
+
520
+ ## W\&B Integration
521
+
522
+ Log scalars, vectors, images, and videos directly to Weights & Biases:
523
+
524
+ ```python
525
+ import goggles
526
+ from PIL import Image
527
+ import numpy as np
528
+
529
+ # Start or switch a W&B run
530
+ goggles.new_wandb_run(name="exp-run", config={"lr":1e-3, "batch":32})
531
+
532
+ # Scalars & histograms
533
+ goggles.scalar("accuracy", 0.92)
534
+ goggles.vector("loss_curve", [0.5,0.4,0.3])
535
+
536
+ # Images
537
+ img = Image.fromarray((np.random.rand(64,64,3)*255).astype(np.uint8))
538
+ goggles.image("random_image", img)
539
+ ```
540
+
541
+ ## Graceful Shutdown
542
+
543
+ Cleanly handle interrupts (e.g., Ctrl-C) and perform cleanup:
544
+
545
+ ```python
546
+ import goggles
547
+ from PIL import Image
548
+ import numpy as np
549
+
550
+ # Start or switch a W&B run
551
+ goggles.new_wandb_run(name="exp-run", config={"lr":1e-3, "batch":32})
552
+
553
+ # Scalars & histograms
554
+ goggles.scalar("accuracy", 0.92)
555
+ goggles.vector("loss_curve", [0.5,0.4,0.3])
556
+
557
+ # Images
558
+ img = Image.fromarray((np.random.rand(64,64,3)*255).astype(np.uint8))
559
+ goggles.image("random_image", img)
560
+
561
+ ```
562
+
563
+ ## Asynchronous Logging & Video
564
+
565
+ Offload heavy logging tasks to worker threads and log video sequences:
566
+
567
+ ```python
568
+ import goggles, numpy as np, time
569
+ from PIL import Image
570
+
571
+ goggles.new_wandb_run("video_demo", {})
572
+ goggles.init_scheduler(num_workers=4)
573
+
574
+ def save_and_log_frame(frame, idx):v
575
+ path = f"/tmp/frame_{idx}.png"
576
+ frame.save(path)
577
+ goggles.image(f"frame_{idx}", frame)
578
+
579
+ for i in range(100):
580
+ arr = (np.random.rand(64,64,3)*255).astype(np.uint8)
581
+ img = Image.fromarray(arr)
582
+ goggles.schedule_log(save_and_log_frame, img, i)
583
+ goggles.scalar("queue_size", goggles._task_queue.qsize())
584
+
585
+ goggles.stop_workers()
586
+ ```
587
+
588
+ ## Full list of running examples
589
+
590
+ We prepared an `examples/` folder with scripts covering:
591
+
592
+ TODO
593
+
594
+ ## Contributing
595
+
596
+ PRs, issues, and feature requests are welcome! Open an issue or submit a PR on GitHub.
597
+ See our [contributing guide](./CONTRIBUTING.md) for more information.
598
+
599
+ ## License
600
+
601
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.