Is there a way to do "digital phosphor" type of plots for time series data?

Modern (digital) oscilloscopes usually perform a simulation of a phosphor-based CRT. These are described by various terms like digital phosphor, intensity grading, color temperature display, etc.

These algorithms don’t simply draw a uniform intensity pixel at each point the waveform sweeps through but draw brighter pixels where the signal dwells or repeatedly visits, or use a color map to convey similar information. For waveforms with a lot more points than plot width and particularly for modulated waveforms this can give a much better depiction.

Does anyone know if this has been implemented somewhere in the Julia plotting universe?

1 Like

This idea reminded me of Persistence Spectrum in Signal Analyzer - MATLAB & Simulink… it would indeed be awesome to have this capability.

1 Like

I implemented this idea a while ago based on this great post:

I’ve put my old notebook here: windytan.ipynb · GitHub

Here’s a picture:

4 Likes

Very cool!, Thank you for sharing!

I don’t have other solutions regarding “digital phosphor” plots themselves, but I might have other tools you might want - and a possible home for the solution you might find/develop.

Basic eye diagrams

I have implemented an algorithm to “fold” a time-domain waveform into multiple waveform sections in PhysicalCommunications.jl:
GitHub - JuliaTelecom/PhysicalCommunications.jl: Tools for development & test of PHY communication layer

There are also PRBS (LFSR) sequence generators in that module.

An eye diagram functionality is also built into the CMDimData.EasyPlot module made available through CMDimCircuits.jl:
GitHub - ma-laforge/CMDimCircuits.jl: Parametric analysis/visualization of model/measurement/simulation results

This version is particularly useful if you run parametric sweep simulations. (I should also try to consolidate the two implementations, but I haven’t put in the energy to find an adequate layering)

A home for “digital phosphor” algorithms

If you eventually do implement this algorithm and wish to find a home for it, I think the JuliaTelecom group would be a good place to start looking:
GitHub - JuliaTelecom/PhysicalCommunications.jl: Tools for development & test of PHY communication layer

If you think it is better housed somewhere else, that’s fine. But I would at least like to add it to our list of available tools found here:
GitHub - JuliaTelecom/JuliaTelecom: Greetings & telecom-related tools

2 Likes

Thank you! I was poking around JuliaTelecom the other day - I have an RTL-SDR that I’ve played with for a couple of years and an RSP1A on the way just now and want to play with SDR in Julia eventually!

Regarding the phosphor emulation - I am thinking about using FFTResampling to upsample if necessary, forming a image in which each pixel location holds a count or dwell value, then mapping that to a color space. I previously figured out how to plot on an image using Plots.jl (for a chromaticity diagram) which would allow plotting of other things (e.g. results of math performed on the waveform) in the coordinate space of the data.

If I sweep a Gaussian spot, pick the right green, emulate misfocus and astigmatism, I should be able to get quite realistic old school scope effect. That’s taking it a bit farther than necessary but might be fun!

1 Like

A simple digital phosphor oscilloscope:

using Plots; gr(bg=:black, legend=false)
N=1000; Nc=100;  # number of points; number of curves
t = LinRange(0, 6, N)
y = [ sin.(t .+ rand()/2) for i in 1:Nc]
p  = plot() 
for i in 1:Nc
     plot!([t, t], [y[i], -y[i]], lc=:yellow,lw=2,la=2/Nc)
end
display(p)

1 Like

That’s cool and I would say you’re emulating persistence!

Edit: Your trigger is not very stable! :grinning:

1 Like

Here’s what I did:

using Colors
using ImageIO
using ImageShow
using Plots

signal(t) = (1.0+0.75*sinpi(2*1000*t))*cospi(2*1.12e6*t)

begin
	xres = 600
	yres = 256
	counts = zeros(yres,xres)
	Nsamples = 1_000_000
	Ts = 2e-9
	tfinal = (Nsamples-1)*Ts
	tpixel = (tfinal+Ts)/xres
	nbits=8
	Vdiv = 0.5
	Vquant = 8*Vdiv/2^nbits
	sampled_signal = Int.(2^(nbits-1) .+ round.(signal.(0.0:Ts:tfinal)/Vquant))
	for i in 1:Nsamples
		t = i*Ts
		x = min(xres,Int(t÷tpixel)+1)
		counts[sampled_signal[i],x] += 1
	end
	counts = Int.(round.(counts./(maximum(counts)/255)).+1)
	cmap = reverse(colormap("Greens",256; logscale=true))
	img = map(c->cmap[c], counts)
end

which gives
image

2 Likes

You can also use the shading capabilities of GR:

using WAV
using GR

file = joinpath(dirname(Base.find_package("GR")), "..", "examples", "Monty_Python.wav")
y, fs = WAV.wavread(file)

shade(y, colormap=-GR.COLORMAP_BLUESCALE, ylim=(-1,1))

Could also be read with FileIO, but seems to be buggy (see !307).

1 Like

@jheinen, is it possible to set a black background in GR.shade?
PS: very nice example with a “cerise sur le gâteau” (Monty Python’s song)

Sure, you simply have to change the color scheme (usecolorscheme(2)) and remove the minus sign for the colormap:

1 Like

Looks much better with huge wave files:

2 Likes

@jheinen, this is brilliant.

But it takes longer to read the file (~5.7s) than to display it (~1.1s) …

julia> version()
"0.55.0.post17"

julia> versioninfo()
Julia Version 1.5.3
Commit 788b2c77c1 (2020-11-09 13:37 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin18.7.0)
  CPU: Intel(R) Core(TM) i9-10910 CPU @ 3.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
Environment:
  JULIA_DEPOT_PATH = /usr/local/lib/julia:
  JULIA_NUM_THREADS = 10
  JULIA_HISTORY = /Users/jheinen/.julia/logs/repl_history.jl

  5.702187 seconds (138.76 M allocations: 3.156 GiB, 7.05% gc time)
  1.137098 seconds (344 allocations: 893.807 MiB, 1.43% gc time)

julia> 
1 Like

I’m late to the party, but these excellent visualizations make me want to recreate the “oscilloscope music” from Jerobeam Fenderson - Blocks - YouTube in a more accurate style. I wrote a very simple wav → video converter here: Oscilloscope Music ← but my persistence implementation was pretty basic.

1 Like

@klaff, yes it is a very cheap model and with only one setting: persistence = +∞
:sweat_smile: