This release of Visdom covers all work merged since v0.2.4 (February 2023), spanning roughly three and a half years of accumulated development. It introduces a pluggable persistence layer, a new experiment-tracking API, eight new visualization types, HTTPS and OpenAPI support, and a broad set of stability and security fixes across the plotting, embeddings, and socket layers. End-to-end testing is mid-migration from Cypress to Playwright, with both suites present, and a Python unit test suite (pytest) has been added alongside them.
Before You Upgrade
This is a large release. Most of it is additive, but five areas can affect an existing setup. Please read this section before running pip install -U visdom.
1. Python 3.12 is now required
visdom now declares python_requires>=3.12. On Python versions below 3.12, current versions of pip will not install v0.3.0 and will retain or select the newest compatible Visdom release, typically v0.2.4. Installers that do not enforce Requires-Python may install v0.3.0, but importing it on Python versions below 3.12 will fail.
The six dependency has also been dropped.
What to do: upgrade the interpreter first, and prefer a clean virtualenv over upgrading in place:
python3.12 -m venv .venv && source .venv/bin/activate
pip install -U visdom2. vis.win_hash() and the /win_hash endpoint are gone
Any code calling vis.win_hash(), or any non-Python client hitting /win_hash, will break. The capability is superseded by a window-versioning scheme that requires no client-side call.
Existing saved environment files are not affected — window versioning is backwards compatible with the old JSON format.
3. openTSNE is now a hard install dependency
The embeddings pane moved off the CUDA-only tsnecuda onto openTSNE. This removes the CUDA requirement, but openTSNE is now a required dependency pulled in by every install, including on machines that never open an embeddings pane. It ships compiled extensions, so expect a longer install (and a build step on platforms without a matching wheel).
4. The server no longer fails when its port is busy
Previously, starting the server on an occupied port raised. It now logs a warning and binds an arbitrary free port assigned by the OS instead:
WARNING:root:Port 8097 is already in use, assigning a free port
The actual port is printed in the startup banner. If scripts, containers, monitoring, or clients assume 8097, they will not automatically follow the new port and may fail or connect to the process already occupying 8097. Verify that the configured port is free before starting Visdom and check the actual URL printed at startup. There is no strict-port option in v0.3.0.
5. Authentication behaviour changed on two paths
- Unauthenticated requests now return HTTP 401 instead of 400. Any client that branches on a
400status to detect an auth failure needs updating. WrappedSocketWrap.postnow requires authentication. This endpoint was previously reachable without credentials; unauthenticated automation against a login-enabled server will now be rejected. (This was a security hole — see Security.)
What does not break
To be explicit, since this release is large:
- Saved environments load as-is. The new
DataStorelayer preserves the classic one-file-per-environment layout under~/.visdom/. No migration step, no export/reimport. - The default port (
8097), hostname, and env path (~/.visdom/) are unchanged. - Existing environment JSON files remain compatible with the new window versioning.
- Server login credentials are established at startup, either interactively or, when
VISDOM_USE_ENV_CREDENTIALSis enabled, throughVISDOM_USERNAMEandVISDOM_PASSWORD. A new environment-based deployment also requiresVISDOM_COOKIEwhen no cookie-secret file exists. The switch to salted PBKDF2 hashing requires no password-hash migration.
Highlights
- Pluggable storage backend — environment persistence now runs through a
DataStoreabstraction instead of ad hoc JSON file I/O. - Experiment tracking API — new
vis.experiment/vis.log_metrics/vis.finish_experimentclient methods and a server-side/experiments/logendpoint. - Eight new visualization types: Sankey diagrams, violin plots, confusion matrices, ROC/PR curves, 2D histograms, 3D line plots, formatted HTML tables, and parallel coordinates.
- HTTPS support, a health check endpoint, and a full OpenAPI 3.1 specification for language-agnostic API access.
- PyTorch and scikit-learn logging integrations (
VisdomLogger,VisdomSklearnLogger) for automatic training-metric logging. - New workspace features: undo for closed panes, batch environment delete, save-all-environments, per-pane comment threads, and a toast notification system.
- Security hardening: salted PBKDF2 password hashing, authentication added to a previously-unprotected socket endpoint, and consistent HTTP 401 responses for unauthenticated requests.
- Embeddings pane rewritten onto
openTSNEand Three.jsBufferGeometry, fixing WebGL context leaks and idle CPU usage. - End-to-end tests being migrated from Cypress to Playwright (both suites currently ship), plus a new pytest-based Python unit test suite.
- Minimum supported Python is now 3.12; the
vis.win_hash()API and/win_hashendpoint have been removed — see Before You Upgrade.
Features
Architecture & Persistence
Environment storage was previously implemented as direct JSON file reads/writes scattered across the server's request handlers. It's now routed through a Components affected:
Related Pull Requests:
DataStore interface with a JSONStore implementation that preserves the existing one-file-per-environment on-disk layout, so existing saved environments continue to load without migration. Environment saves, loads, deletes, layout persistence, undo stacks, and the remaining read paths (environment listing, comparison) were incrementally moved onto the new abstraction. This also lays the groundwork the Experiment Tracking API is built on.
Technical details
py/visdom/data_model/ (new)
py/visdom/server/handlers/web_handlers.py, socket_handlers.py
py/visdom/utils/server_utils.py
Experiment Tracking
Adds Components affected:
Related Pull Requests:
vis.experiment, vis.log_metrics, and vis.finish_experiment client methods that post to a /experiments/log Tornado handler. The handler persists experiment metadata and metrics through an ExperimentStore layered on top of the DataStore, and mirrors the data into in-memory environment state so a full-environment save preserves it. Once an experiment is marked finished or failed, further writes are rejected with HTTP 409; malformed requests return 400, and finishing a nonexistent experiment returns 404. The endpoint respects server-wide readonly mode (returns 403).
Technical details
py/visdom/experiments/ (models, store)
py/visdom/server/handlers/web_handlers.py — ExperimentLogHandler
py/visdom/server/app.py, openapi.yaml
Visualizations
Eight new plot and pane types:
- Sankey diagrams via
vis.sankey() - Violin plots via
vis.violin() - Confusion matrix panes with a Python API helper
- ROC and precision-recall curve panes with Python API helpers
- 2D histograms via
vis.histogram2d() - 3D line plots, extending
vis.line() - Formatted HTML tables via
vis.table() - Parallel coordinates plots, aimed at multi-metric experiment comparison
New options on existing plots: an opts.caption option available on all plots; a free-draw annotation mode with its own update pipeline; alpha-channel support for images; per-point/per-label markersize arrays; tight_layout support to reduce plot margins; store_history for scatter/line plots; a learning curve convenience method; heatmap overlays for images via vis.image_heatmap(); caption support on audio and video panes; float Y-axis labels for scatter plots; and programmatic saving of Plotly figures to image files.
Export & Sharing
Pane-level export to PNG/JPG/SVG, and an option to export a plot's metadata alongside its data. You can also export a full environment to a standalone HTML file, or use the "Save All Environments" action.
Workspace & Environments
- Undo for closed panes — recover a pane after accidentally closing it.
- Batch delete for environments.
- Comment box on every pane.
- Toast notification system, used for backend errors and recovery events.
- Environment groups now start minimized by default.
- Environment filtering and a
get_env_stateclient API. - Environment names shown in legend titles when comparing environments.
- LaTeX rendering in the Properties Pane.
- Automatic restore of saved environments on server start.
- Upload a JSON file to load an environment from the UI.
- Environment names too long to be a valid filename are stored as
hash_<sha256>.jsonwith the real name kept inside the file. Normally-named environments keep their existing<name>.jsonfilename. - Text panes gained auto-scroll-to-bottom and clipboard-copy support.
Technical details
Related Pull Requests: #1446, #1175, #1608, #1586, #1592, #1356, #1318, #1323,
#1646, #1293, #1209, #1242, #1046, #1312
Image Handling
Side-by-side image comparison across environments, a reworked image slider (update_image_slider API, fixing three related bugs), and image selection (image_select() / image_update_selected).
Embeddings
Lasso-selection interactions were extended with a closing-circle indicator and a minimum-selection hint.
Related Pull Requests: #1475
Technical details
Framework Integrations
Two new logging integrations for training loops: VisdomLogger for PyTorch training metrics, and VisdomSklearnLogger for scikit-learn estimator and cross-validation search results.
API & Server
- HTTPS support, configurable via
run_server.py. - Health check endpoint for monitoring server liveness.
- OpenAPI 3.1 specification (
openapi.yaml) documenting the full HTTP API. - Port validation for the
-portargument, and an automatic fallback to a free port when the configured one is in use — see Before You Upgrade, as this changes startup behaviour.
Improvements
Embeddings
The CUDA-only tsnecuda dependency was replaced with openTSNE; the pane was migrated to Three.js BufferGeometry; rendering is now on-demand rather than continuous; embeddings updates were moved outside the generic update flow; and event registration can now be disabled.
Client Architecture
Related Commits:
main.js and related panes were migrated from class components to functional React components with hooks, the client was upgraded to React 17, the socket/relayout callback system was reimplemented, server-communication logic was extracted into a dedicated class, and unused dependencies (md5, json-stable-stringify, react-select) were removed. Server-side socket handler code was also deduplicated.
Technical details
de687db migrate main.js to functional react
a85e9e6 upgrade to react 17
910339c reimplement setState-callbacks
34d62c3 reimplement relayout using the new callbacks-loop
fa1a3a8 extract server-communication into a dedicated class
8f697cc deduplicate server-side socket handler code
Server & Performance
Incoming-message handling is now shared between the WebSocket and polling transports; socket handler initialization was consolidated into a common base handler; message dispatching was extracted out of the Poller; and update_packet payloads are built without a full deepcopy.
Bug Fixes
Socket & Connectivity
PID-based lazy reconnection to prevent client hangs on stale connections; isolated event-subscriber errors; standardized JSON message serialization; FIFO message ordering for polling mode; capped unbounded in-memory data growth; fixed crashes from invalid saved layout JSON (now recovered with a toast); guarded against KeyError when an environment or pane is deleted mid-request; fixed live updates not applying in compare-mode subscriptions; fixed malformed update requests returning the wrong status code; and improved WebSocket disconnect/error handling.
Plotting & Rendering
Fixed NaN/Inf handling across several code paths (a new NanSafeEncoder replaced ad hoc nan2none() handling; np.nanmin/nanmax in histograms; missing-X-value guards in smoothing and dual-axis-line rendering); fixed crashes on resizing 3D surface, heatmap, and contour plots; fixed a crash when scatter() was called with zero rows; fixed a crash when deleting a trace by name if two traces shared a name; fixed real-time updates on network graph panes; fixed a division-by-zero in quiver-plot normalization; fixed multi-environment window rendering aggregation; fixed a list index out of range crash in /update; fixed decoding of Plotly 6's binary array format; fixed plotlyplot ignoring configured width/height; fixed blank PNG exports for network graphs; fixed inconsistent export file naming; fixed cross-talk between environments; and fixed 3D scatter Z-axis updates.
Environment & UI
Fixed a blank screen after deleting an unrelated environment; fixed the environment search input and console warnings in the environment tree; fixed keyboard input being swallowed in property fields; fixed pane titles moving during resize and panes collapsing below minimum size; fixed the resize arrow overlapping in comparison view; fixed a crash on Windows when APPDATA was missing; fixed one environment's name being a prefix of another causing overwrite on disk; and fixed several NaN/type-validation gaps.
Image Handling
Negative float pixel values no longer wrap on the cast to uint8 (a new opts.normalize option was added alongside this fix); grayscale/RGB/RGBA formats are handled explicitly, fixing a size-1-dimension collapse bug; float images slightly above 1.0 are clamped with a warning; and duplicate/overlapping captions in image and image-compare panes were fixed, along with an invalid CSS value and unnecessary re-renders on mouse movement.
Embeddings
Fixed a WebGL context leak on unmount and clamped pane dimensions to avoid GL_INVALID_VALUE errors on resize; fixed lasso-selection drilldown losing focus.
Security
- Password hashing strengthened: salted PBKDF2-HMAC-SHA256 (100,000 rounds) instead of unsalted SHA-256. Credentials are established at server startup, so no action is required on upgrade.
- Closed an unintentionally unauthenticated socket endpoint:
WrappedSocketWrap.postnow requires authentication. This can break unauthenticated clients — see Before You Upgrade. - Consistent status codes: unauthenticated requests return HTTP 401 instead of 400. Clients branching on
400need updating. - Authorization checks consolidated into a shared handler helper, and a message-validation fix closed a cross-environment data leak.
Testing
The end-to-end suite is being migrated from Cypress to Playwright. The Playwright suite now covers panes, properties, text, uploads, modals, export, polling, and screenshot-based visual regression, and runs alongside the remaining Cypress specs ( Related Pull Requests:
npm test still runs Cypress; npm run test:pw runs Playwright). Image and parallel-coordinates specs have not been ported yet. A Python unit test suite using pytest was also added, along with dedicated unit tests for the core plotting functions (bar, histogram, boxplot, surface, contour, and others).
Technical details
Documentation
- Added a public documentation site built with Docusaurus, published via GitHub Pages.
- Added
AGENTS.md, documenting contribution and AI-agent guidelines for the repository. - Added a standardized agent skills scaffold and AI usage guidelines for first-time contributors.
- Synced the plotting API documentation and Python type stubs with the README, and added missing type stubs for newer visualization methods.
- Numerous README/CONTRIBUTING clarity and typo fixes, including a clarified environment-hierarchy explanation and a clarified image shape (C × H × W) requirement for
vis.image.
Technical details
Related Pull Requests: #1467, #1246, #1301, #1477, #1494, #1418, #1445, #1099,
#1159, #1045, #1495, #982, #946, #947, #1124, #1514
Breaking Changes & Upgrade Notes
Everything that needs action before upgrading is written out in full at the top: Before You Upgrade. Reference details:
| Change | Impact | Reference |
|---|---|---|
Minimum Python version is now 3.12; six dropped
| v0.3.0 cannot be installed or imported on older Python versions | #1527 |
vis.win_hash() and /win_hash removed
| Calling code breaks; saved files unaffected | b4ec4c84, 23353464
|
openTSNE now required (replaces tsnecuda)
| Larger install; CUDA no longer required | #1500 |
| Busy port falls back to an OS-assigned free port | Server no longer fails on a taken port | #1351 |
| HTTP 401 replaces 400 for unauthenticated requests | Clients branching on 400 break
| #1396 |
WrappedSocketWrap.post requires authentication
| Unauthenticated clients break | #1511 |
Technical details
Related Commits:
b4ec4c84api-change: remove/win_hashendpoint23353464api-change: add window versioning (backwards compatible with old JSON files)5d5de4d5implement versioning for_pendingPanes
Contributors
Thanks to everyone whose work is included in this release:
@ali0786mehdi, @ArnavBallinCode, @Ashishat404, @Bekka592, @da-h, @Debajeet-1411,
@hpdang, @Jayantparashar10, @Manik-Khajuria-5, @marcoag, @mariobehling, @norbusan,
@omkarsureshs, @rajnisht7, @Saksham-Sirohi, @sumedhaagh, @SxxAq, @TahoorBR,
@tayyabazahid147-art, @tonypzy, @vanshika-hgnis, @vedansh-5, @Vidhushaaa30, @vish-4-1
New Contributors
Visdom changed hands after v0.2.4, and almost everyone above is here for the first
time. Making their first contribution to the project in this release:
@ali0786mehdi, @ArnavBallinCode, @Ashishat404, @Bekka592, @Debajeet-1411, @hpdang,
@Jayantparashar10, @Manik-Khajuria-5, @marcoag, @mariobehling, @norbusan,
@omkarsureshs, @rajnisht7, @Saksham-Sirohi, @sumedhaagh, @SxxAq, @TahoorBR,
@tayyabazahid147-art, @tonypzy, @vanshika-hgnis, @vedansh-5, @Vidhushaaa30, @vish-4-1