Visashot, a screenshot python script for vintage instruments

~~~ Latest release: 1.20 (2024-01-26) ~~~
Download visashot.py
86 instruments supported!

Hewlett-Packard HP54542A screenshot with Python
Spoiler alert: CRT-green is awesome

Old Hewlett-Packard gear with CRTs or LCDs were not equipped with the now ubiquitous USB memory port to save screenshots. Some later HP models started to have 3.5" floppies, and while it's a great way to pull screenshots out it's still a bit messy: need to source a 3.5" drive and floppies, slow bit rate,... However, many of those instruments will also happily print over parallel, serial or HP-IB. The latter is the weapon of choice here: your vintage lab probably has a few IEEE-488 cables lying around and GPIB remains a great interface for controlling test and measurement gear (fear not, other interfaces are also supported, see below!) Unfortunately the 'print' function was originally meant as a real print-to-printer function, and good luck finding and maintaining an old HP plotter or printer that supports PCL.

How cool would it be to be able to capture a screenshot from the instrument directly from your PC? In my book that qualifies as instant nerd creds. But unfortunately (again) there is very little info online on how to do this. Keysight still has a remanent on how to get the data, but the sample programs are in weird old BASIC and not up to date with current programming languages/standards. Plus forget about multi-platform. Time to get our hands dirty!

Internal greasy bits

The interesting bit I gathered from the link above is that the "PRINT?" command (or sometimes "*SYSTEM:PRINT?" command) is what can be used to get the data, at least on my 53310A. IOW, the example shows that you ask the device to print (to a printer) but then somehow get the data yourself through GPIB. Weird? Indeed. In this case the interrogation mark in the command is the key: it means we want an answer, and that answer is just the printing data! (Not adding the "?" would probably print to a printer, but I'm not sure.) Using Python and pyvisa, this simply translates to:

device.write("SYSTEM:PRINT?")
buffer = device.read_raw()

...and that's it! The screenshot data is now in the buffer variable. Given that these older devices were built before PNG and JPEG were invented, there is no standard format and in this case you're likely to get PCL (but other stuff could come out too). In addition, some devices may return bitmap data in PCL, other will return line graphics. How can you convert the PCL buffer to a bitmap or vector graphic file that's usable today? Ghostscript has the ghostpcl tool to output to PDF and other formats and that's a good way to start probing. After saving the python buffer to a file (in binary mode!) we can use ghostpcl to view our first screenshot:

ghostpcl -sDEVICE=pdfwrite -sOutputFile=screenshot.pdf buffer.pcl

This works fine but there's a few little problems. First the data I get from both my 53310A and 54542A are bitmap screenshots. No need of a PDF for that! An image would be simpler. Secondly the 'plot' appears with seemingly random margins on a much larger 'white' page, ready to be printed in a printer that doesn't exist. That's not very user friendly. It'd be much nicer to get that binary data directly from the PCL data. Can we reverse engineer it? Of course! After a bit of swearing at the hex editor this is how the PCL data buffer is formatted as far as I can tell:

  • A header section. Interesting bits start with 0x1B 0x2A. We can find "r640S" and "rA", the first one indicating the image stride (row length + padding), the second one just indicates the end of the header. Thus, here's a typical header for the 53310A screenshot:
    0D 0A 0A 0A 0A 1B 2A 72 36 34 30 53 1B 2A 72 41
  • A number of image rows. Also always starting with 0x1B 0x2A, followed by "b74W". Here the 74 is the width of the image row in bytes. Since the display is not even grayscale, 1 bit = 1 pixel, and thus we have 74x8 = 592 pixels in width. Note that the 74 (as well as the 640 above) are ASCII strings and not HEX values.
  • Finally we have a footer which looks like this:
    1B 2A 72 42 0A 0A 0A 0A 0A
    Again, we have 0x1B 0x2A and the end of the file is indicated by "rB"

The output from the 54542A is a bit more complicated as it adds the time and date of capture, as well as some parameters like vertical and horizontal scales. But restricting ourselves to the "0x1B 0x2A" bits nicely trims off all that stuff. Also, the 54542A reports a stride (in the header) of 640 but an image row width of 648. The data is indeed 81 bytes long (81*8=648pixels), but the last byte (8 pixels) is blank and not useful. Thus the actual image width can be set to the minimum of the header stride and the row width.

Armed with this information we can write a nice python script that parses the PCL data and saves it as a PBM file, a natural choice for 1-bit-per-pixel data. The PBM data can then be processed in any image editor like Gimp or XV. But since we're likely to want to do the same edits over and over, why not include that in our python script? ImageMagick to the rescue, and now the background and foreground colors as well as a surrounding margin can be modified. Plus the output is in a more modern/compressed/universal PNG format.

Screenshots gallery

Here are some results for a few devices. Dat green CRT look... NOICE.

Hewlett-Packard HP-53310A screenshot with Python
HP-53310A Modulation Domain Analyzer

Hewlett-Packard HP-54542A screenshot with Python
HP-54542A 500MHz 2Gs/s Oscilloscope

Hewlett-Packard HP-8594A screenshot with Python
HP-8594A Spectrum analyzer (Thanks Wilko!)

Note the slightly squished aspect ratio of the last screenshot. Some CRTs have non-square pixels so the buffer aspect ratio does not match what is seen on the instrument's screen. For 85xx instruments for example it seems the pixels have an aspect ratio of 5:4 (1.25), which is hard to render nicely on a PNG without the image getting blurry in one direction. To solve this problem without adding fuzzyness we can simply upscale the image using an integer factor. This is done manually usint the -p option (command line) or 'scale' argument (python call).

Hewlett-Packard HP-54641D screenshot with Python
HP-54641D Mixed signal oscilloscope. Note that the scope does not export a grayscale image over HPIB :-(

For supported LCD-based devices that follow the architecture of the HP4396B the output is in TIFF format which makes things quite a bit easier. Plus we of course gain color, but the actual resolution is smaller than the older CRT devices because the menu area is sometimes blanked (and thus can be trimmed). A large part of the LCD is also unused due to the LCD being larger than the physical viewport/frame of those devices. In other words a lot of pixels are lost...

Hewlett-Packard HP-4396B screenshot with Python
HP-4396B Spectrum/Network/Impedance Analyzer. Menus are often not shown in the TIFF, so auto-trimming to image content was used.

Hewlett-Packard HP-4352B screenshot with Python
HP-4352B PLL/VCO Signal Analyzer, without any trimming. This shows the amount of unused display area, especially on the right and bottom edges.

Usage

The screenshot script is normally invoked from the command line. Use './visashot.py -h' for instructions. A typical use case would be:

$> ./visashot.py -d 8 -x xv -o output.png -g

where '-d' selects the HP-IB address of the device, '-x' opens the image in the specified program (here 'xv') and '-o' selects the output filename. This filename also lets you choose the image type depending on its extension. Lossless formats like PNG, PPM, TIFF or BMP are strongly recommended. Lossy formats like JPEG will result in poor quality. The last option, '-g' is actually not typical at all, and is used to show debug messages. Useful when something goes wrong! For interfaces other than HP-IB a VISA identifier string can also be used. For example, for a serial device on COM1:

$> ./visashot.py -s "ASRL1::INSTR" -o output.png

Alternatively, visashot can also be imported and used in your own python code, for example to periodically take a screenshot of an instrument. Here's a typical invocation:

import pyvisa
import visashot
ressourceManager = pyvisa.ResourceManager()
instrument = ressourceManager.open_resource("GPIB0::7::INSTR")
visashot.capture(instrument, "screenshot.png", margin = 10)

Compatible devices

This screenshot python script currently supports the following devices

  • HP-53310A Modulation domain analyzer
  • HP-5452/4xA Oscilloscopes (54520A, 54522A, 54540A, 54542A)
  • HP-54600 Oscilloscopes (54600A, 54601A, 54602A, 54600B, 54601B, 54602B, 54603B, 54610B, 54615B, 54616B, 54645A, 54645D)
  • HP-5461/2xA/D Oscilloscopes (54621A/D, 54622A/D, 54624A, 54641A/D, 54642A/D)
  • HP-4395A and HP-4396B Spectrum / network / impedance analyzers
  • HP-4352B VCO/PLL analyzer
  • HP-859xE Spectrum analyzers (8591E, 8593E, 8594E, 8595E, 8596E)
  • HP-859xEM EMC analyzers (8591EM, 8593EM, 8594EM, 8595EM, 8596EM)
  • HP-859xL Spectrum analyzers (8590L, 8592L)
  • HP-856xE Spectrum analyzers (8560E, 8561E, 8562E, 8563E, 8564E, 8565E)
  • HP-E444xA PSA Signal analyzers (E4440A, E4443A, E4445A, E4446A, E4447A, E4448A)
  • HP-E440/1xB ESA Spectrum analyzers (E4401B, E4402B, E4403B, E4404B, E4405B, E4407B, E4408B, E4411B)

In addition, the following devices should work but are untested. If you have any of those please give them try and get in touch.

  • HP-8990A and 8991A Peak power analyzers
  • HP-8992A Digital video power analyzer
  • HP-5450/1xA/B Digital oscilloscopes (54501A, 54503A, 54504A, 54505A/B, 54506B, 54510A/B, 54512B)
  • HP-54620A Logic analyzer
  • HP-83475B Lightwave communication analyzer
  • HP-4286A RF LCR meter
  • HP-4291A/B Impedance / material analyzer
  • HP-4294A Precision impedance analyzer
  • HP-E4406A VSA Signal analyzer
  • HP-894xx Signal analyzers (89441A, 89440A, 89410A, 89441V, 89450A, 89451A)

You can also access the list of these 80+ compatible devices from the command line with the '-C' option:

$> ./visashot.py -C

If you have an instrument that is not listed and you know which GPIB commands to send to capture a screenshot, please get in touch and we'll figure it out. Note that while all supported instruments are from Hewlett-Packard at this time, there is nothing preventing this script to be extended to other brands. Please consider contributing if you have a device from Tektronix, Advantest, NF, Anritsu, Rohde & Schwartz, Yokogawa, LeCroy...

Requirements

This scripts has minimal requirements:

  • Python 3
  • The Pyvisa Python bindings package
  • ImageMagick
  • By default Pyvisa will use a pure python implementation ("pyvisa-py", a python package which you will likely want to install). This pure Python backend should work for 99% of use cases and doesn't require proprietary libraries. Optionally you can install backend VISA implementations from National Instruments (NI-VISA) or Keysight, among others. A detour visit to the IVI foundation may also be interesting.
  • For Unix users the default backend can be changed by defining the PYVISA_LIBRARY environment variable, e.g. in your ~/.bashrc. For example, if you want the IVI backend to be your default:
    export PYVISA_LIBRARY='@ivi'
    
  • For linux users, the Linux GPIB library will be required for GPIB / HP-IB instruments. It contains user-space libraries that interface with Pyvisa as well as kernel modules for most IEEE-488 interface cards. I also recommend adding the following line to your rc.local or systemd startup scrips:
    sudo gpib_config --minor 0
    

Testing your PyVISA setup

To test your Pyvisa setup you can run the 'pyvisa-info' utility. This will return the list of backends (py, ivi, ...) as well as the supported interfaces (USB. TCPIP, GPIB,...) Another test is to list the devices detected by PyVISA. This can be done in Python:

import pyvisa
pyvisa.ResourceManager('@py').list_resources()

where the backend '@py' can be changed if you have another backend available. This will produce a list like this:

('ASRL/dev/ttyS0::INSTR', 'GPIB0::1::INSTR', 'GPIB0::3::INSTR',
'GPIB0::6::INSTR', 'GPIB0::9::INSTR', 'GPIB0::10::INSTR',
'GPIB0::11::INSTR', 'GPIB0::12::INSTR', 'GPIB0::13::INSTR')

These identifier strings can be used for capturing a screenshot using the '-s' option. Note that the serial port on my machine (/dev/ttyS0) is reported as an instrument even if it is unconnected. Not sure why but at least now you know what string to use for your serial instruments. If an expected instrument is not listed then you may need to check your connections, cables, instrument power, PyVISA installation,... Final note regarding this command: on my machine I get a lot of warnings "libgpib: invalid descriptor". Don't worry about those, they are harmless and part of the device discovery process.

Version history

  • 1.20: Fix support for E44xx analyzers
  • 1.19: Improve Windows and ImageMagick V7 compatibility
  • 1.18:
    • Support for 5460xA series oscilloscopes, 54620A logic analyzer and 83475B lightwave communication analyzer
    • Change argument name margin_width -> margin
    • Minor code improvements
  • 1.17:
    • Allow pixel aspect ratio to be specified
    • Stop using the return-to-local command as it can paralize the entire bus
    • Code cleanup, as usual
  • 1.16:
    • Bug fixes and style improvements
    • Don't crop menu section for HP-4396B & friends
    • Scale, crop and margins are now all available for both PCL and bitmap devices.
    • Improve bitmap buffer capture for GPIB devices
    • Cropping to content available on all formats
    • Fix addition of borders for transparent colors
    • Name changed to visashot, less generic and unique for pip
  • 1.15:
    • Add support for more scopes of the 54600 series
    • Access to non-gpib instruments via full VISA resource strings
    • Minor fixes
  • 1.14: Return instrument to LOCAL after the screenshot is captured
  • 1.13: Bug fixes
  • 1.12:
    • Add premilinary support for 89400 series of signal analyzers
    • Minor bug fix and code beautification
  • 1.11: Add experimental support for several devices: older oscilloscopes, 899x power analyzers and E44xx PSA/VSA/ESA analyzers
  • 1.10:
    • Single-pass processing of PCL buffers
    • 5462x and 5464x scopes compatibility
  • 1.9:
    • Add debug mode (-g option)
    • Add backend selection option
    • Improvements with HP spectrum analyzers (85xx family)
    • Add -I option to avoid spurious on-screen error messages with some instruments
    • Fix ImageMagick conversion on Windows
  • 1.8:
    • Improve windows compatibility
    • Improve recently added 85xx devices handling
  • 1.7:
    • Add compatibility with spectrum/EMC analyzer series 8590E, 8590EM, 8590L and 8560E.
    • Preliminary windows support
  • 1.6:
    • Code cleanup
    • Improve model parsing
    • In addition to the classic command-line invocation, the code can now also be imported in your own scripts.
  • 1.5: Support for the 42xx and 43xx series impedance/network/material/PLL analyzers
  • 1.4:
    • Allow other visualization programs
    • Allow running without specified output file
  • 1.3: Code refactoring
  • 1.2: Fix typo in 54520A detection
  • 1.1: Auto-detection of instrument type
  • 1.0: First release

Special thanks

  • Ketut Wiadnyana for doing the ground work for 85xx series compatibility as well as Windows compatibility.
  • Wilko PA1WBU for a ton of help, comments and captured data regarding the same 85xx series as well as the 5462/4x series scopes and some screenshots.
  • Joshua Wright for inspiration regarding the E44xx screenshot capture.
  • Gerry Hawkins for RS232 debugging and feature suggestions.
  • Romain Grobéty for hp5460xA debug and testsing.
  • Mankan Gustafsson for testing, debugging and sharing code for E44xxB analyzers (@mankan on EEVblog forums)

Contributing

For developers, you can access the Sourceforge project page with GIT repository and subscribe to the mailing list.

The main goal at the moment is to increase the number of supported devices. Please consider contributing by:

  • Verifying compatibility with tested devices and reporting bugs
  • Testing devices that I think are supported (but are not tested yet)
  • Adding support for other devices. Coding not always necessary, sometimes just sharing a good information and testing is all that is required!
  • Submitting sexy screenshots

TODO and future ideas

Besides adding support for more instruments this is also on the TODO list:

  • Add visashot to pip repository.
  • Use a better format to hold Visashot's "intellligence" (which device requires which commands and processing). Maybe a JSON file or some python structure that is better than the current static global variables. This will progressively grow in importance as more devices are added.
  • Better "Pythonic" code and style. This is ultra-low priority at the moment.

The tool hp2xx could be used to extract vector graphics from some devices instead of the raster images we are currently grabbing. More interesting discussions on the EEVblog forums and this page which captured vector plots over the serial interface.