PyDebugScope
See inside your tensors and DataFrames while debugging Python.
A VS Code extension for data scientists. While a Python program is paused in the debugger (debugpy / the
Python extension), it shows arrays, tensors and DataFrames as interactive plots and tables.

Open a visualizer
- Variables / Watch view: right-click a variable and pick Open in PyDebugScope.
- Editor: select an expression (or put the cursor on a name) and press Ctrl+Alt+V (macOS: Cmd+Alt+V), or
use the context menu.
- Command palette / debug toolbar: PyDebugScope: Visualize Expression… accepts any Python expression,
for example
batch["image"][0], df[df.loss > 1] or model.fc.weight.grad.
The expression is evaluated in the stack frame selected in the Call Stack view. Open panels re-evaluate on every
step. You can edit the expression in the panel header.
Views
| Value |
View |
| numpy, torch (CPU/GPU, bf16, sparse, requires_grad), jax, tensorflow, cupy, xarray, PIL images, masked arrays, numeric lists |
Array |
| pandas DataFrame/Series, polars DataFrame/Series, list of dicts, 2-D string/object arrays |
Table |
| matplotlib Figure/Axes, seaborn grids, plotly figures |
Figure |
dict, list, tuple, objects, nn.Module (parameters and buffers) |
Explorer: click an entry to open it in a new panel, Ctrl+click to open it in the current one |
Array view
- Stats bar: shape, dtype, device, memory, min/max/mean/std, NaN and inf counts, fraction of zeros.
- Signal: pick the x axis and an optional channels axis. Every other axis gets an index slider (◀ ▶ step
buttons included). Long signals are min/max-decimated so peaks are kept, and zooming in fetches
full-resolution samples. Optional sample rate
fs (time axis in seconds), stacked channels, markers, log y.
- Image: pick the H/W axes and an optional RGB(A) axis. HWC, CHW and NCHW layouts are detected
automatically. Colormaps (viridis, magma, turbo, gray, RdBu, …) with color ranges: min–max, robust 1–99 %,
symmetric ±max, dtype/[0,1], or manual. Log scale, NaN shown as transparent, pixel values on hover.
Pan and zoom work as in any Plotly chart.
- Histogram of the whole array or of the current slice. Integer data uses one bin per value.
- Spectrum: FFT magnitude along the x axis (two-sided for complex input).
- Complex input: choose abs, real, imag or phase.
- Copy data puts the shown data on the clipboard as TSV. Plotly's camera button saves a PNG.
Table view
Filtering, sorting and paging all run on the Python side, so DataFrames with millions of rows stay responsive.
- Global search across all columns. pandas query box (
DataFrame.query syntax).
- Per-column filters:
>0.5, 1..10, >0 & <1, !=3, null, !null, text, !text, =exact,
/regex/i, A | B.
- Click a header to sort (ascending, descending, off).
- Selection: drag or shift-click a range, Ctrl+C copies TSV (pastes into Excel or Sheets),
Ctrl+Shift+C copies with headers, Ctrl+A selects the page.
- Copy… menu: selection, page, all filtered rows as CSV, TSV or Markdown, or the current filters as
pandas code.
- Export… saves the filtered rows as CSV, JSON or TSV.
- Σ column statistics: describe(), histogram, most frequent values. Click a frequent value to filter on it.
- Double-click a cell to see the full value, with Filter = value and Exclude value buttons.
- Plot: 📈 in a column header, or the Plot panel. Line, scatter, bar, histogram, box or violin. The x axis can
be the row number, the index or any column, with optional color by a column. Plots use the filtered data.
- Show or hide columns (for wide frames), set significant digits, and color numeric cells with heatmap.


Large data (multi-GB arrays and frames)
The visualizer never copies a large object as a whole, neither inside the debugged process nor into VS Code:
- Lazy slicing. torch (CPU and GPU), tensorflow, jax, cupy, dask, xarray, h5py / zarr datasets and numpy
memmaps are indexed natively; only the part being displayed is converted to numpy (for GPU tensors, only that
part is copied to the host).
- Window. Arrays larger than
pyDebugScope.autoWindowMB (default 256 MB) open with an automatically reduced
window, which crops the leading (batch / time) axes first. A banner says so. Edit the window per axis
(start:stop:step, a single index, or :), go back to the suggestion, or load the Full array. For any
other array the editor is behind the Window… button. Statistics, sliders, plots and hover use the window
but show original indices.
- Bounded work. Statistics and histograms use strided samples (marked ≈ sampled). Signal envelopes are
computed in chunks (very long ranges first use a strided subset, which the UI notes). Spectra of very long
signals average 16 segments of 2²⁰ samples, and are peak-pooled for display.
- Transfer budget. No result exceeds
pyDebugScope.maxTransferMB (default 32 MB). Images are strided to fit,
and copy/export of too many rows is refused with alternatives: copy only the first N rows that fit, or
write the file directly from Python (in row chunks, without going through VS Code).
- Tables. Filters, search and sorting work on row positions and process strings in row chunks, so the
DataFrame is not copied. polars frames and lists of dicts above the auto-window size are converted to pandas
only in row blocks, with previous / next / all rows controls.
- Freeze copies the value only up to the auto-window size; larger objects are frozen by reference (the
panel says so).


Measured with a real 1.6 GB float32 array in a debugpy session (test/dap-big-test.mjs): inspect, signal,
histogram and image views add at most ~40 MB to the debuggee's peak memory, the spectrum ~170 MB, and every
result sent to VS Code is under 2 MB.
Freeze and compare
❄ Freeze stores a snapshot (a copy) of the value inside the debuggee and stops auto-refreshing that panel.
The ⧉ button opens a second, live panel for the same expression, so you can compare before and after a step.
Settings
| Setting |
Default |
Meaning |
pyDebugScope.autoRefresh |
true |
Re-evaluate open panels when the debugger stops |
pyDebugScope.maxTransferMB |
32 |
Largest result sent from the debuggee to VS Code |
pyDebugScope.autoWindowMB |
256 |
Arrays / converted tables above this open with a reduced window; largest object Freeze copies |
pyDebugScope.maxPlotPoints |
200000 |
Points per signal / column plot before decimation |
pyDebugScope.maxImageSide |
2048 |
Images are strided above this side length |
pyDebugScope.tablePageSize |
200 |
Rows per table page |
pyDebugScope.defaultColormap |
viridis |
Colormap for single-channel images |
How it works
On first use, the extension installs a small helper module (python/pydebugscope_helper.py, standard library
only; numpy and pandas are imported lazily) into the debuggee's sys.modules through a DAP evaluate request.
Later requests call that module, which slices, decimates and filters the data where it lives. Results come back
as base64 chunks, so debugpy's repr truncation can't corrupt them. Your program and its environment are not
modified otherwise. The helper works with Python 3.8 and later.
Development
npm install
npm run build # or: npm run watch
# F5 in VS Code -> "Run Extension (opens examples/)" -> debug examples/demo.py
uv venv .venv && uv pip install --python .venv/bin/python numpy pandas polars debugpy matplotlib pillow
npm run test:python # helper unit tests + memory guardrail tests
npm run test:dap # end-to-end against a real debugpy adapter (incl. a 1.6 GB array)
.venv/bin/python test/harness/server.py 8765 # webview in a browser with a mocked VS Code API
npm run package # builds a .vsix (needs `npx @vscode/vsce`)
| |