Pyviewer is a python library for easily visualizing NumPy arrays and PyTorch tensors.
A viewer for showing single fullscreen images or line plots without other UI elements. Runs in a separate process and remains interactive even if the main process is suspended (e.g. in a debugger). Great for interactively looking at intermediate values of complex ML/CG/CV pipelines.
Usage:
from pyviewer import single_image_viewer as siv
siv.draw(img_chw=np.random.randn(3,64,64))
siv.plot(np.sin(2*np.pi*np.linspace(0, 1, 10_000)))
A viewer that shows ImGui UI elemets on the left, and a large image on the right. Runs in the main process, but supports visualizing torch tensors directly from GPU memory (unlike single_image_viewer).
A viewer for large, multidimensional PyTorch tensors. Runs in a separate thread
in the same process with pydev_do_not_trace = True, keeping its UI interactive
at PyDev/debugpy breakpoints. Dimension radio buttons choose X, Y and an optional
channel axis; sliders select the remaining slice indices and R/G/B channels.
The image, axes controls, and GPU timings are separate dockable windows; use
the View menu to reopen a closed window. Unlock the top-right L/U button to
reveal the UI scale slider (0.1 to 5.0);
right-click the slider to reset to 1.0.
Drag to pan, scroll to zoom, double-click to reset, or right-click to snap scale.
Press P/Pause to toggle updates and N/Space to accept one tensor while paused.
While paused, rendering drops to 5 FPS after two seconds without input and stops
entirely after three minutes. Mouse movement, clicks, scrolling, or keyboard input
wakes the viewer. Tensor publication does not reset the inactivity timer; new
references remain pending while rendering is stopped. Unpaused rendering continues
normally.
from pyviewer import tensor_viewer as tv
# NCHW: show batch index 0 and choose the displayed RGB channels.
tv.draw(tensor, x_dim=3, y_dim=2, channel_dim=1, channels=(0, 1, 2))
# Or select a grayscale plane, defaulting to the final two dimensions as Y/X.
tv.draw(other_tensor)
tv.inst.wait_for_close() # optional: keep the script alive until the window closesAfter Esc or window close, tv.draw() and tv.init() leave the viewer closed.
Call tv.show_window() (or tv.inst.restart()) to open it again.
The viewer retains a detached reference to the tensor, including its original
device and strides. Cropping, integer-stride slicing and transposing create views;
only the visible samples are packed for texture upload. CUDA tensors use direct
CUDA–GL interop; CPU tensors upload from host memory. Each
texture dimension is bounded by the framebuffer size and GL_MAX_TEXTURE_SIZE.
Zoomed-out sampling approximates nearest sampling on a regular integer lattice;
zoomed-in sampling uses every visible source pixel. Automatic normalization uses
visible samples only, so the intensity range can change while panning. Turn it
off in the controls (or use tv.init(normalize=False)) to select a fixed display
range with the two-value slider. Its defaults are [0, 1] for float tensors and
[0, 255] for uint8 tensors. The float slider spans [-2, 2]; Ctrl-click a slider
value to enter a wider limit. Turning automatic normalization off copies its last
visible sample limits into the sliders.
Selections persist across calls with the same shape when selection arguments are
omitted. In-place updates appear on subsequent frames; coordinate concurrent
writes if a consistent frame is needed. For CUDA, finish producing the tensor
before publishing it (for example with torch.cuda.synchronize()), as the viewer
uses its own thread and stream. The UI thread is a daemon, so the script must
remain running to keep the window open. Use one active threaded viewer at a time;
GLFW/ImGui have process-global state. Background GLFW window handling works on
Linux/Windows; macOS requires it on the main thread.
- Makes use of imgui-bundle to provide plotting support and other extras
- Dynamically rescalable user interface
- Window resizing to integer multiple of content resolution
- Pan and zoom of the main image
Install with pip install pyviewer. To use the optional Torch custom interop
operations, install pip install 'pyviewer[custom-ops]' so that the Ninja build
tool is available.
Requires Python 3.11+.
The development shell provides pyviewer and its Python dependencies as Nix-managed packages:
nix developPython 3.13 is the default. Use nix develop .#python311, .#python312, or
.#python314 to select another supported interpreter.
Entering a development shell refreshes .nix-python/bin/python, a local link
to the shell's Python for VS Code interpreter selection and debugging.
Build the package with nix build. The legacy nix-shell and
nix-build default.nix -A pyviewer entry points are also available.
Versioned flake packages such as nix build .#pyviewer313 are provided for
Python 3.11 through 3.14. With the legacy shell, select a version with
nix-shell --argstr pythonVersion 3.12.
The package set can also be imported by another Nix expression:
pyviewerPackages = import /path/to/pyviewer {
inherit pkgs;
pythonPackages = pkgs.python312Packages;
};Use pyviewerPackages.pyviewer in python.withPackages. The Nix package set
uses pinned upstream imgui-bundle wheels, currently available for CPython
3.11 through 3.14 on x86_64 Linux and x86_64/aarch64 macOS 14 or newer.
See examples/demo.py for a usage example.
PannableArea::screen_to_uv_xform
Maps absolute screen coordinates (e.g. imgui.get_mouse_pos()) to transformed image UVs, useful for picking etc.
PannableArea::uv_to_screen_xform
Maps image UVs to absolute screen coordinates. Useful when combined with imgui's draw lists.
PannableArea::get_visible_box_image()
Returns the top-left and bottom-right UV coordinates of the currently visible image region.
PannableArea::get_hovered_uv_image()
Returns the image UVs that lie under the mouse cursor.
PannableArea::get_hovered_uv_canvas()
Returns the canvas UVs that lie under the mouse cursor. Differs from the image UVs in case of non-matching image and window aspect ratios.
from pyviewer.single_image_viewer import draw; draw(img_chw=...)
One-liner that opens a new viewer (unless already open) and draws the provided image. Runs in a separate process and thus works even when execution is halted by e.g. a debugger.
