Skip to content

v0.4.1 release notes

v0.4.1 was released on October 9, 2026. It is v0.4.0 with one fix and nothing else: a compressed DICOM frame is now held to the image its header describes. Updating is recommended if you open files you did not create.

Compressed frames are held to their header (v0.4.1)

Section titled “Compressed frames are held to their header (v0.4.1)”
  • A compressed frame whose pixel data declares a different image than the file’s header is refused instead of being decoded at the size the pixel data declares. Before, a small file could make the viewer hold gigabytes of memory for one frame, and a JPEG frame larger than its header ended in an internal error. This affected v0.4.0 and earlier, for JPEG, JPEG-LS, JPEG 2000, and JPEG XL frames and for Deflated Image Frame Compression.
  • Such a frame answers 500 pixel_decode_failed with “pixel data disagrees with the header”. The file stays listed and its other frames display. Compared are rows, columns, samples per pixel, and the sample depth against Bits Allocated, and the frame’s tile, packet, and scan structure against fixed limits (4,096 tiles, 100 scans).
  • A JPEG 2000 frame stored as a JP2 file is decoded from its codestream alone: a palette or channel definition in the file’s other boxes is no longer applied.
  • A JPEG XL frame with an alpha channel, floating-point samples, more bits than Bits Allocated, or an animation is refused. So is an 8-bit JPEG frame under a 16-bit header, which was displayed as noise.
  • Files that differ from their header only in Bits Stored, sign, colour transform, resolution levels, or chroma subsampling display as before. No command-line option, Python parameter, or VS Code setting changed.

See troubleshooting when a file you rely on is refused.

The rest of this page describes v0.4.0, which v0.4.1 includes unchanged.

v0.4.0 was released on October 8, 2026. Every /api request now requires an access token, --unix-socket serves the viewer through a private Unix domain socket for shared servers, and --cache-budget sets the frame cache memory. Opening the viewer from the terminal, from Python view(), or in VS Code needs no change. Scripts that call the HTTP API directly must be updated.

  • Each dcmview process generates a token and requires Authorization: Bearer <token> on every request under /api, including /api/health and the ROI export, over TCP and Unix sockets. A local user or a web page that can reach the loopback port can no longer read file lists, tags, or pixels.
  • The printed launch link carries the token in its fragment (http://127.0.0.1:PORT/#token=…). The viewer takes it out of the address bar, keeps it for the tab across reloads, and sends it with every API request.
  • Opened without the token, or after dcmview restarted on the same port, the page says to open the link printed in the terminal instead of showing a load error. Pasting that link into the same tab loads the viewer.
  • DCMVIEW_TOKEN fixes the token. --no-token turns the check off, with a warning, for a listener behind a proxy that already authenticates.
  • The warning for a non-loopback bind now says that plain HTTP does not encrypt the access token or DICOM data. It says the endpoints are unauthenticated only under --no-token.

See ephemeral state and network security for the model and its limits.

Breaking change: scripts that call the HTTP API

Section titled “Breaking change: scripts that call the HTTP API”

Scripts that call the API directly must send the token. Without it every /api path, unknown routes included, answers 401 with the error code unauthorized and WWW-Authenticate: Bearer. The token is read from the Authorization header only; a query parameter or cookie does not work.

Before (0.3)
dcmview --no-browser --port 8888 ./study &
curl http://127.0.0.1:8888/api/files
After (0.4), with a token you choose
export DCMVIEW_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
dcmview --no-browser --port 8888 ./study &
printf 'Authorization: Bearer %s\n' "$DCMVIEW_TOKEN" |
curl --header @- http://127.0.0.1:8888/api/files

The header is piped so that the token does not appear in curl’s arguments.

To use the generated token instead, read it from the startup line: dcmview --no-browser --startup-json ./study prints a server_started JSON line with base_url, token, and protocol. Append API paths to base_url, never after the #token=… fragment of url. In Python, non-blocking handles expose the same values as handle.base_url and handle.token; see the Python reference.

Other things to know when migrating:

  • The printed URL and the startup url now end in /#token=…. Treat that link and the startup JSON as credentials.
  • DCMVIEW_TOKEN must be non-empty and use only A-Z a-z 0-9 - . _ ~. An invalid value, or the variable set together with --no-token, stops startup. The token is never read from the command line.
  • --no-token restores the open API of 0.3. Use it only behind something that authenticates.
  • A bookmarked http://127.0.0.1:PORT/ without the fragment shows the access-link page.

The API remains an unsupported internal protocol.

  • On Linux and macOS, dcmview --unix-socket ~/dcmview/scan.sock ./study listens on a socket only your account can reach, and prints the ssh -L 8080:<socket> user@host command and the link to open.
  • The directory must be yours and not writable by group or others; a missing one is created with mode 0700. The socket has mode 0600, connections from another user ID are dropped, and a lock file beside the socket stops a second launch from replacing a running viewer’s socket. Both files are removed on shutdown.
  • Socket mode conflicts with --host and --port, always bypasses VS Code routing, and never opens a browser.

See Use a private Unix socket on a shared server.

--cache-budget BYTES sets one total for the display, raw, and overlay frame caches, such as 256MiB. The minimum is 16MiB and the default remains 704 MiB. It limits cached frames, not total process memory. See the CLI reference.

Non-blocking handles gain read-only token and base_url properties for authenticated API calls. handle.url is still the launch URL. Older binaries, and viewers routed into VS Code through the bridge, leave the new properties as None.

  • The extension forwards the viewer’s origin and attaches the token afterwards, so Remote-SSH port forwarding keeps working. It still works with older binaries.
  • The output channel does not show the token.
  • --unix-socket is not supported in dcmview.extraArgs: the extension needs an HTTP viewer URL and reports an unsupported launch.
  • The printed viewer URL (dcmview: server running at ...) now ends in /#token=....
  • The SSH forwarding hint is followed by a then open http://localhost:PORT/#token=… line to copy on your local machine.
  • A failed browser launch no longer prints the opener’s error text, which could contain the launch URL. It says to open the launch URL manually.
  • A redaction box change could leave a frame cached without the new box: a frame requested at the same instant as the change could be rendered with the earlier boxes and then served from the cache until the next change. This affected v0.3.2.
  • Viewer shortcuts no longer fire while Ctrl, Cmd, or Alt is held, so Ctrl+Z no longer selects the Zoom tool and Ctrl+R no longer selects the ROI tool before the browser or VS Code handles the combination. [ and ] still step frames when typed with Option or AltGr, and Delete and Backspace still remove the selected ROI with a modifier held.
  • Export ROIs shows a notice in the viewer when the export fails, instead of leaving the browser on an error page or doing nothing.
  • Earlier documentation described a right-drag zoom and wheel frame scrolling that the viewer does not have. Middle-drag pans, the wheel zooms about the pointer, and the wheel steps frames in the Scroll tool. See Adjust image presentation.
  • The annotation CSV reference now states the checks that run when a CSV loads. Box bounds are checked when an edit is saved, not on load.

Apart from the token requirement, no API endpoint or response field changed. The startup JSON only gains fields: base_url, token, protocol, and socket in socket mode. See the internal HTTP protocol.

  • The token does not encrypt anything. Over plain HTTP on a non-loopback bind, the token and the DICOM data are readable on the network. Keep the loopback default and forward with SSH.
  • When dcmview opens the browser itself, the operating system’s opener receives the launch URL, token included, as a process argument that other local users can read on some systems. On a shared machine use --no-browser and open the printed link, or use --unix-socket.
  • A token fixed with DCMVIEW_TOKEN is not rate-limited; use a long random value.
  • The --unix-socket directory needs a filesystem that supports file locks. Some network filesystems do not; use a local directory such as one under $XDG_RUNTIME_DIR. A forced kill can leave a stale socket, which the next launch on that path replaces. Forwarding a Unix socket needs OpenSSH 6.7 or newer on both ends.
  • --unix-socket is not available on Windows, from Python view(), or in the VS Code extension.
  • DCMVIEW_TOKEN set only in a terminal or notebook is not passed through the VS Code bridge. A viewer the extension manages inherits the extension host’s environment and otherwise generates its own token, and a Python handle routed into VS Code has token and base_url set to None.
  • The limits of v0.3.2 still apply: masking is a display aid, not de-identification. It does not change pixels, free text, or paths, and redaction boxes are not saved.
  • The other known limitations still apply, including the uninterpreted VOI LUT Function and the limits of presentation state annotations.
  • The v0.4.0 Python wrapper and VS Code extension work with older binaries. An older Python wrapper works with a v0.4.0 binary, because the startup url it reads carries the token.
  • An older extension pointed at a v0.4.0 binary through dcmview.binaryPath can lose the token when VS Code forwards the port, as with Remote-SSH. Update the extension.
  • The CLI gains --unix-socket, --no-token, and --cache-budget, and reads DCMVIEW_TOKEN. Existing CLI options and Python keywords are unchanged from v0.3.2.
  • v0.4.0 records no new broad compatibility campaign; see compatibility evidence.

See the documentation source for the exact release commit and media attribution for the release gallery.