Visualization

Open in JupyterLite

The viz module holds DASCore’s plots, and they hang off the object they are of: Patch.viz for data, Spool.viz for what an archive holds, Inventory.viz for the observing system which recorded it.

Patch

The following provides some examples of patch visualization.

Waterfall

The waterfall patch function creates a waterfall plot of the patch data.

import dascore as dc

patch = dc.get_example_patch('example_event_2')

# Default scaling uses IQR-based fence to handle outliers
patch.viz.waterfall(show=True);

Controlling color scaling

The scale parameter controls the colorbar saturation. By default, waterfall uses a statistical fence (1.5×IQR) to exclude outliers and show the majority of the data clearly.

import matplotlib.pyplot as plt
import dascore as dc

patch = dc.get_example_patch('example_event_2')

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))

# Relative scaling: 0.2 means ±20% of dynamic range around mean
patch.viz.waterfall(scale=0.2, scale_type="relative", ax=ax1)
ax1.set_title("Relative scaling (scale=0.2)")

# Absolute scaling: directly set colorbar limits
patch.viz.waterfall(scale=(-50, 50), scale_type="absolute", ax=ax2)
ax2.set_title("Absolute scaling (scale=(-50, 50))")

plt.tight_layout()
plt.show()

Wiggle

The wiggle patch function creates a wiggle plot of the patch data. We’ll use the same patch as above to model this function.

import dascore as dc

patch = (
    dc.get_example_patch('example_event_1')
    .set_units("mm/(m*s)", distance='m', time='s')
    .taper(time=0.05)
    .pass_filter(time=(..., 300))
)
patch.viz.wiggle(scale = .5)

Another example using wiggle to plot a sine wave is demonstrated below.

import dascore as dc

patch = dc.examples.get_example_patch(
    "sin_wav",
    sample_rate=60,
    frequency=[60, 10],
    channel_count=1,
)
patch.viz.wiggle(show=True);

Inventory

An inventory plots what it knows about the fiber, with no data present. The examples below use the tunnel deployment the tunnel recipe builds.

import dascore as dc

inventory = dc.get_example_inventory("tunnel")

Path

The path plot draws every track along the fiber against optical distance. distance= is the window to draw, in the same coordinate; this deployment starts with 1.5 km of telemetry lead-in, which would otherwise crush the instrumented part into a corner.

inventory.viz.path(time="2024-07-01", distance=(1495, 1780), show=True);

The boreholes label themselves 3, 2, 1 — the fiber works back through them — and the splices are ticks rather than zero-width boxes.

Map

The map plot draws where the fiber physically goes, in two axes of the inventory’s coordinate reference system. x and y choose them: a borehole runs straight down, so the default plan view collapses it to a point.

inventory.viz.map(x="x", y="z", color="section", time="2024-07-01", show=True);

The break near the middle of the trench is the slack coil, which nobody surveyed. Unplaced fiber is left out rather than bridged, since a made-up polyline would be worse than a gap.

color= also takes a geometry column, a label group, or "coupling". Its default is optical distance, which is how a distance read off a waterfall is found on the ground:

inventory.viz.map(x="x", y="z", time="2024-07-01", show=True);

Timeline

The timeline plot draws when each acquisition and each optical path was valid. An epoch which states no start or no end is unbounded, and runs off that side of the axis hatched.

inventory.viz.timeline(show=True);

The path lane splits on the first of September, which is the day the trench cable was repaired.

Spool

A spool plots what it has and what it is missing, reading its index rather than its data, so these work on an archive far too large to load. The examples use a sparsely sampled deployment example: two months of a temperature and a strain acquisition, sampled once an hour.

import dascore as dc

spool = dc.get_example_spool("sparse_dss")

Coverage

The coverage plot gives each group of patches which could combine a lane, drawn as the runs it holds and the holes between them. The holes are the ones get_gaps reports and the percentage is that lane’s get_coverage, so the picture and the numbers cannot disagree.

spool.viz.coverage(show=True);

Both acquisitions lose the four days in the middle of January, which is the site’s own outage; the strain acquisition started later and was pulled out earlier, which is why its lane is shorter rather than gappy at the ends.

Any dimension the spool states can be measured, and a (start, end) pair windows it:

spool.viz.coverage(time=("2024-01-15", "2024-01-25"), show=True);

Calendar

Over months, one lane per group runs out of room. The calendar plot gives each day a cell instead, colored by how much of that day the spool covers:

spool.viz.calendar(show=True);

A cell has no room for a lane per group, so it asks whether anything was recording: a day is the union of what every group covers. Two acquisitions running at once therefore cover one day between them, not two, and a cell never exceeds 100%. A grey cell is a day the calendar has no room for — there is no thirtieth of February — which is a different claim from a day covered by nothing.

That also means group is not a way to pick one acquisition here; it only decides which boundaries count as gaps. Select the patches you mean first, and the calendar is of them alone:

spool.select(tag="temperature").viz.calendar(show=True);

The four days in the middle of January are still empty, since that outage took the whole site, but the strain acquisition no longer fills in the days temperature was down on its own.

method="gap" asks the opposite question on a log scale, which is the one to use when the holes are short enough that a percentage rounds them away:

spool.viz.calendar(method="gap", show=True);

method="count" colors each day by how many patches overlap it, which is how an archive’s file layout shows itself.