Grounding Line Picker Demo¶
This notebook demonstrates the frame.pick xarray accessor for interactive grounding line picking on radar echograms.
Features:
- Interactive point picking with click-to-add
- Layer overlays (surface, bottom) with visibility toggles
- Snap-to-layer functionality for precise picking
- Automatic uniform-spacing interpolation (matching the MATLAB imb.picker)
- Undo/Clear controls
- CSV export for picked points
Run with juv:
juv run docs/notebooks/grounding_line_picker_demo.ipynb
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "xopr-viewer @ git+https://github.com/developmentseed/xopr-viewer",
# "jupyterlab",
# ]
# ///
import holoviews as hv
import panel as pn
import xopr
# Import accessor to register it
import xopr_viewer # noqa: F401
hv.extension("bokeh")
pn.extension()
Connect to OPR¶
Establish an OPR session with a local cache directory for faster subsequent requests.
# Establish an OPR session with caching
opr = xopr.OPRConnection(cache_dir="radar_cache")
Load Radar Data¶
Query and load frames for a specific segment.
# Select a segment
selected_collection = "2022_Antarctica_BaslerMKB"
selected_segment = "20230109_01"
print(f"Selected segment: {selected_segment}")
# Query frames
stac_items = opr.query_frames(
collections=[selected_collection], segment_paths=[selected_segment]
)
print(f"Found {len(stac_items)} frames")
stac_items.head(3)
# Load the radar data
frames = opr.load_frames(stac_items)
print(f"Loaded {len(frames)} frames")
Merge Frames¶
Combine individual frames into a continuous flight line for easier visualization.
# Merge frames into a single flight line
flight_line = xopr.merge_frames(frames)
flight_line
Load Layers¶
Get existing layer picks (surface, bottom) from OPR.
# Load layers for the merged flight line
layers = opr.get_layers(flight_line)
print(f"Available layers: {list(layers.keys())}")
Quick Overview Plot¶
For large datasets, it helps to make a quick downsampled plot first to
identify the region of interest, then render that region at full resolution.
Using x_mode="rangeline" avoids the uniform-spacing interpolation step
(see Axis Modes below), making it the
fastest option for a first look.
# Quick overview: downsample by taking every 10th trace
step = 100
overview = flight_line.isel(slow_time=slice(None, None, step))
print(
f"Full resolution: {flight_line.sizes['slow_time']} traces x {flight_line.sizes['twtt']} samples"
)
print(
f"Downsampled: {overview.sizes['slow_time']} traces x {overview.sizes['twtt']} samples"
)
overview.pick.plot(layers=layers, x_mode="rangeline", width=900, height=300)
# Pick a range-line region from the downsampled overview and convert to
# full-resolution indices. The overview's range-line axis shows indices
# into the downsampled array, so multiply by `step` to get back to the
# original trace positions.
rl_start, rl_end = 200, 225 # range-lines from the overview plot
roi_start = rl_start * step
roi_end = rl_end * step
region = flight_line.isel(slow_time=slice(roi_start, roi_end))
# Slice layers to match the region's time range. Layers have different
# sampling than the echogram, so use .sel() on the slow_time bounds.
t_start = region.slow_time.values[0]
t_end = region.slow_time.values[-1]
region_layers = {
name: layer.sel(slow_time=slice(t_start, t_end)) for name, layer in layers.items()
}
print(
f"Overview range-lines {rl_start}-{rl_end} → "
f"full-res traces {roi_start}-{roi_end} ({region.sizes['slow_time']} traces)"
)
region.pick.plot(layers=region_layers, width=900, height=400)
Interactive Picker Panel¶
Use region.pick.panel() for the full interactive picking interface:
- Layer checkboxes: Toggle visibility of surface/bottom layers
- Snap to layer: When enabled, clicks snap to the nearest visible layer
- Slope checkboxes: Toggle a slope subplot below the echogram for any layer
- Smoothing slider: Control the rolling-mean window size for slope smoothing
- Picks counter: Shows current number of picked points
- Undo/Clear: Remove last point or clear all
- Export: Save picks to CSV
Click on the echogram to add picks!
layout = region.pick.panel(layers=region_layers, width=900, height=400)
layout
Axis Modes and Interpolation¶
The viewer supports multiple x-axis and y-axis display modes. Some modes require interpolation to produce a uniform grid for Bokeh's image glyph, which adds processing time. Understanding this helps when working with large datasets.
X-axis modes¶
| Mode | Display coordinate | Interpolation | Notes |
|---|---|---|---|
rangeline |
Integer trace index | None | Fastest -- just assigns float indices |
gps_time |
datetime64 timestamps |
Uniform resampling | CReSIS data often has non-uniform slow_time spacing; resampled to median step via linear interpolation |
along_track |
Cumulative distance (km) | Uniform resampling | Computed from lat/lon via polar stereographic projection, then resampled to uniform spacing |
Y-axis modes¶
| Mode | Display coordinate | Interpolation | Notes |
|---|---|---|---|
twtt |
Two-way travel time (µs) | None | Direct coordinate swap |
range_bin |
Integer sample index | None | Just assigns float indices |
range |
One-way range (m) | None | Linear transform of twtt (twtt * c / 2) |
elevation |
WGS-84 elevation (m) | Vertical regridding | Calls interpolate_to_vertical_grid() -- per-trace conversion using aircraft elevation, surface twtt, and ice velocity |
surface_flat |
Depth below surface (m) | Vertical regridding | Same regridding as elevation, then shifted by mean surface range |
The rangeline + twtt combination is the fastest since it requires no
interpolation at all. The elevation and surface_flat y-modes are the
most expensive because they regrid every trace onto a common vertical axis.
# View picks as DataFrame
print(f"Number of picks: {len(layout.picker.points)}")
layout.picker.df
Picking Workflow¶
Typical workflow for labeling grounding points:
- Load radar data and merge frames into a flight line
- Load layers (surface, bottom) for reference
- Quick overview: Downsample and plot with
x_mode="rangeline"to find region of interest - Slice region: Convert overview range-lines to full-res indices, slice data and layers
- Open the picker:
layout = region.pick.panel(layers=region_layers) - Enable snap (optional): Check "Snap to layer" to snap clicks to visible layers
- Click on echogram to add picks at grounding point locations
- Export to CSV using the Export button
The exported CSV contains:
id: Unique identifier for each pickslow_time: Time coordinate (x-axis)twtt: Two-way travel time in seconds (y-axis)