Tutorial 1: Loading and plotting

Loading data and parameters

First, it is important to know that each grid has channels and parameters. A channel is a 1D array of data over each point in the x,y grid. A parameter is one value for each point in the x,y grid.

In most cases, channels and parameters have different names, e.g. the channel "Current" and the parameter "Z offset", but in some cases the same names are used.

OK, let's look at some examples.

julia> using SpmGrids
julia> grid = load_grid("Bias_spectroscopy.3ds")SpmGrid("Bias_spectroscopy.3ds", sweep: "Bias", 10 channels, 128 points, 20x20 pixels)
julia> parameter_names(grid) # available parameter names18-element Vector{String}: "Sweep Start" "Sweep End" "X" "Y" "Z" "Z offset" "Settling time" "Integration time" "Z-Ctrl" "Final Z" "Scan:Current" "Scan:Applied Voltage measured" "Scan:Bias" "Scan:Z" "Scan:Phase" "Scan:Amplitude" "Scan:Frequency Shift" "Scan:Excitation"
julia> channel_names(grid) # available channel names10-element Vector{String}: "Current" "Applied Voltage measured" "Bias" "X" "Y" "Z" "Phase" "Amplitude" "Frequency Shift" "Excitation"
julia> grid.size, grid.size_unit, grid.center, grid.angle, grid.pixelsize # grid parameters([1.5e-8, 1.5e-8], "m", [1.10662e-7, 2.33166e-7], 0.0, [20, 20])
julia> grid.start_time, grid.end_time # more parameters(Dates.DateTime("2017-03-09T21:19:38"), Dates.DateTime("2017-03-10T08:21:16"))
julia> grid.header # even more parametersOrderedCollections.OrderedDict{String, String} with 111 entries: "Grid dim" => "20 x 20" "Grid settings" => "1.106620E-7;2.331660E-7;1.500000E-8;1… "Filetype" => "Linear" "Sweep Signal" => "Bias (V)" "Fixed parameters" => "Sweep Start;Sweep End" "Experiment parameters" => "X (m);Y (m);Z (m);Z offset (m);Settli… "# Parameters (4 byte)" => "18" "Experiment size (bytes)" => "5120" "Points" => "128" "Channels" => "Current (A);Applied Voltage measured … "Delay before measuring (s)" => "1.000000E+0" "Experiment" => "Grid Spectroscopy" "Start time" => "09.03.2017 21:19:38" "End time" => "10.03.2017 08:21:16" "User" => "" "Comment" => "KPFM" "Bias>Bias (V)" => "200E-3" "Bias>Calibration (V/V)" => "1E+0" "Bias>Offset (V)" => "0E+0" ⋮ => ⋮
julia> has_channel(grid, "Current") # true if the grid has a channel named "Current"true
julia> has_parameter(grid, "Z offset") # true if the grid has a parameter named "Z offset"true

Now let's access the actual data. Just use get_channel and get_parameter:

julia> x = get_channel(grid, "Current");  # `Current` channel for the whole grid
julia> x = get_channel(grid, "Current", 5, 5); # `Current` channel at point 5,5
julia> x = get_channel(grid, "Current", :, 5); # `Current` channel for 5th row
julia> # 20th point of `Current` channel for 5th row x = get_channel(grid, "Current", :, 5, 20);
julia> x = get_channel(grid, grid.sweep_signal, 5, 6); # sweep signal at point 5,6
julia> p = get_parameter(grid, "Z offset", 3, 5); # `Z offset` parameter at point 3,5
julia> # get the first 50 points of the `Current` channel for 5th and 6th rows x = get_channel(grid, "Current", :, 5:6, 1:50);
julia> size(x) # 50 points of data for 20 columns and 2 rows(20, 2, 50)

There is an even easier function get_data that returns a channel or parameter. Just be careful not to mix up channels and parameters.

julia> x = get_data(grid, "Current");  # `Current` channel
julia> x = get_data(grid, "Sweep Start"); # `Sweep Start` parameter
julia> # the following returns the `Z` channel # there is also a `Z` parameter, but channels have precedence x = get_data(grid, "Z");
julia> # we can also access the `Z` parameter: x = get_data(grid, par"Z");
julia> # or, to make sure we can only get a channel x = get_data(grid, ch"Z");

Backward channels can be accessed as follows:

julia> x = get_channel(grid, "Current [bwd]");ERROR: ArgumentError: Channel name "Current [bwd]" not found in the SpmGrid. Available channel names are: Current, Applied Voltage measured, Bias, X, Y, Z, Phase, Amplitude, Frequency Shift, Excitation.
julia> x = get_channel(grid, "Current", bwd=true);┌ Warning: Backward channel for "Current" does not exist. Using forward channel. └ @ SpmGrids ~/work/SpmGrids.jl/SpmGrids.jl/src/SpmGrids.jl:415
julia> x = get_channel(grid, bwd"Current");ERROR: ArgumentError: Channel name "Current [bwd]" not found in the SpmGrid. Available channel names are: Current, Applied Voltage measured, Bias, X, Y, Z, Phase, Amplitude, Frequency Shift, Excitation.
julia> x = get_data(grid, bwd"Z");ERROR: ArgumentError: No channel or parameter with name "Z [bwd]" found. Available channel names are: Current, Applied Voltage measured, Bias, X, Y, Z, Phase, Amplitude, Frequency Shift, Excitation. Available parameter names are: Sweep Start, Sweep End, X, Y, Z, Z offset, Settling time, Integration time, Z-Ctrl, Final Z, Scan:Current, Scan:Applied Voltage measured, Scan:Bias, Scan:Z, Scan:Phase, Scan:Amplitude, Scan:Frequency Shift, Scan:Excitation.
julia> x = get_data(grid, ch"Z"bwd);ERROR: ArgumentError: Channel name "Z [bwd]" not found in the SpmGrid. Available channel names are: Current, Applied Voltage measured, Bias, X, Y, Z, Phase, Amplitude, Frequency Shift, Excitation.

Not every grid has a backwards sweep, though. That is why the above expressions give error messages. yes, that is actually on purpose. So that you can see what happens when you try to access channels that do no exist.

Also, parameters never have extra backward data.

Alright, shuffling data around is fun, but in many cases we want to create a plot. Luckily SpmGrids provides functions to do this fast and easy.

Plotting spectra

First thing to know is that plotting needs a Makie backend. CairoMakie can be used for static publication-quality images, while GLMakie and WGLMakie can be used as interactive backends.

Let's see how it works in detail. First, we plot a spectrum. A spectrum is a plot of one channel against another channel for each point in the grid.

using SpmGrids
using CairoMakie  # use any Makie backend you like

grid = load_grid("Bias_spectroscopy.3ds")

fig = Figure(resolution = (600, 300))
ax = Axis(fig[1, 1])

# line plot of `Current` vs `Bias` for all specified x and y indices
plot_spectrum(grid, "Bias", "Current", 10:12, 12, backend=CairoMakie)

fig
using SpmGrids
using CairoMakie

grid = load_grid("Bias_spectroscopy.3ds")

fig = Figure(resolution = (600, 300))
ax = Axis(fig[1, 1])

# x, y, as well as channel values can be indexed
plot_spectrum(grid, "Bias", "Frequency Shift", 8, 2:6, 20:120, backend=CairoMakie)

# add legend
fig[1, 2] = Legend(fig, ax, "Legend", framevisible=false)

fig

Plotting lines

Line plots plot along lines in the three-dimensional grid that is spanned by the x,y plane, as well as the sweep channel.

We can do different types of lines. For instance - similar to spectra - we can plot a response channel vs the sweep channel for a specific point in the x,y plane.

But lines offer more choices: We can also plot a line of the response channel as a function of the x direction, as shown below.

using SpmGrids
using CairoMakie

grid = load_grid("Z_spectroscopy.3ds")

fig = Figure(resolution = (800, 800));
ax = Axis(fig[1, 1])
# plot the `Frequency Shift` for 120th point in the sweep for the second row
plot_line(grid, "Frequency Shift", :, 2, 120, backend=CairoMakie)
fig[1, 2] = Legend(fig, ax, "", framevisible=false, labelsize=10)

ax = Axis(fig[2, 1])
# plot the `Frequency Shift` at point (2,3) against the sweep signal
# keywords (e.g. `color`) with be passed to the plot function for forward channel
plot_line(grid, "Frequency Shift", 2, 3, :, color="#a0a0a0", backend=CairoMakie)
fig[2, 2] = Legend(fig, ax, "", framevisible=false, labelsize=10)

ax = Axis(fig[3, 1])
# plot the `Frequency Shift` at point (3,2) against `Current` channel
# keywords with suffix `_bwd` (e.g. `color_bwd`) will be passed
# to the plot function for backward channel
plot_line(grid, "Frequency Shift", 3,2, :, sweep_channel="Current", color_bwd="#a0a0a0", backend=CairoMakie)
fig[3, 2] = Legend(fig, ax, "", framevisible=false, labelsize=10)

fig

In all examples above, we use the indices for x, y, and the channel to select one-dimensional data.

Plotting planes

To plot planes, we use indexing to select two-dimensional data. For instance, we can plot the "Frequency Shift" channel at a particular value of the sweep channel as a function of the x and y direction.

using SpmGrids
using CairoMakie

grid = load_grid("Bias_spectroscopy.3ds")

fig = Figure(resolution = (800, 400));
g1 = fig[1, 1] = GridLayout()
g2 = fig[1, 2] = GridLayout()

ax1 = Axis(g1[1, 1])

# plot the `Frequency Shift` for 120th point in the sweep
r1 = plot_plane(grid, "Frequency Shift", :, :, 120,
    ax=ax1, backend=CairoMakie)
ax1.title = r1.plot_label

# add colorbars
Colorbar(g1[1, 2], r1.plot, label=r1.data_label)

fig
# add second plot
ax2 = Axis(g2[1, 1])

# plot `Current` values of the bwd sweep
# for 10th to 100th point in the sweep for the 15th row
r2 = plot_plane(grid, "Current", :, 15, 1:100,
    ax=ax2, bwd=true, colormap=:imola, backend=CairoMakie)
ax2.title = r2.plot_label

Colorbar(g2[1, 2], r2.plot, label=r2.data_label)

# mark 15th row in the first plot
px, py = xyindex_to_point(grid, 1, 15)
hlines!(ax1, py * r1.y_factor, color=:red)  # we need to convert to nm

fig

(Still need to figure out why the colorbars are not aligned.)

Plotting cubes

And finally, we can plot three-dimensional data as cube plots.

using SpmGrids
using GLMakie

grid = load_grid("Bias_spectroscopy.3ds")

fig = Figure(resolution = (500, 400));
ax = Axis3(fig[1, 1], perspectiveness=0.5)

r = plot_cube(grid, "Current", colormap=:Spectral_11, backend=GLMakie)
Colorbar(fig[1,2], r.plot, label=r.data_label)

fig

Cube plot

All the plots can be interactive if you use the right Makie backend.