GUI toolkit for CUDA-optimized image processing workflow orchestration. Built as Orange Data Mining add-on plugins on top of the GRDL library.
GRDK turns GRDL's image processing algorithms into a visual drag-and-drop workflow builder. Load multi-band satellite/aerial imagery, co-register image stacks, chip regions of interest, label training data, build processing pipelines with real-time GPU preview, and publish reproducible workflows — all without writing code.
| Mode | Purpose | Widgets |
|---|---|---|
| GEODEV | Interactive GEOINT development | Image Loader, Stack Viewer, Co-Register, Processor, Orchestrator, Preview, Chipper, Labeler, Project, Publisher |
| Admin | Catalog management | Catalog Browser, Artifact Editor, Workflow Manager, Update Monitor |
# Install in editable mode
pip install -e .
# With dev tools (pytest, black, mypy)
pip install -e ".[dev]"GRDK provides the grdk-canvas command, which launches the Orange Canvas with the PyQt6 backend pre-configured and GRDK widgets registered:
grdk-canvasThis is the primary way to use GRDK interactively. The canvas loads two widget categories:
- GEODEV — Image Loader, Stack Viewer, Co-Register, Processor, Orchestrator, Preview, Chipper, Labeler, Project, Publisher
- GRDK Admin — Catalog Browser, Artifact Editor, Workflow Manager, Update Monitor
Drag widgets onto the canvas, connect them with signal wires, and build image processing workflows visually.
Note:
grdk-canvassetsQT_API=pyqt6and configures the AnyQt backend before importing Orange. Always usegrdk-canvasinstead oforange-canvasto ensure the correct Qt backend.
Run workflows without a GUI:
python -m grdk workflow.yaml --input image.tif --output result.tif
python -m grdk workflow.yaml --input image.tif --output result.tif --no-gpugrdl (processing primitives — no framework awareness)
↓
grdl-runtime (execution framework — no GUI)
├── grdl_rt.execution/ — workflow engine, GPU backend, discovery, DSL
└── grdl_rt.catalog/ — artifact storage, search, updates
↓
grdk (Qt/Orange GUI)
├── viewers/ — embeddable Qt widgets (ImageCanvas, napari viewer, chip gallery)
├── widgets/
│ ├── _signals.py — custom Orange signal types
│ ├── _param_controls.py — dynamic parameter UI builder
│ ├── _display_controls.py — image display settings UI builder
│ ├── geodev/ — 10 GEODEV workflow widgets (ow_*.py)
│ └── admin/ — 4 Admin catalog widgets (ow_*.py)
├── _launcher.py — grdk-canvas entry point
└── _pyqt6_bootstrap.py — PyQt6/AnyQt backend setup
tests/ — pytest suite
docs/ — architecture and API documentation
- Execution and catalog logic lives in grdl-runtime (
grdl_rtpackage) — GRDK widgets import fromgrdl_rt.executionandgrdl_rt.catalog, not from local directories viewers/are standalone Qt widgets, embeddable in any Qt parent — no Orange dependencywidgets/are OrangeOWBaseWidgetsubclasses that composeviewers/andgrdl_rt
| Dependency | Purpose |
|---|---|
| grdl-runtime | Execution framework (workflow engine, GPU backend, catalog, discovery) |
| grdl | Image I/O, processing algorithms, coregistration |
| numpy, scipy | Array operations |
| PyQt6 | Qt6 widget toolkit |
| orange3, orange-widget-base | Orange Canvas widget framework |
| napari | Stack viewer with polygon drawing |
GRDK includes a standalone geospatial image viewer for SAR, EO/IR, and multispectral imagery. Launch it from the command line:
grdk-viewer # empty window
grdk-viewer /path/to/image.tif # open a file on startup
grdk-viewer /path/to/product_dir # open a product directory (SAFE, BIOMASS, etc.)The viewer opens any format supported by GRDL's reader library:
| Format | Reader | Notes |
|---|---|---|
| GeoTIFF | GeoTIFFReader |
Single- and multi-band |
| NITF / SICD | SICDReader |
SAR complex data with full SICD metadata |
| SIDD | SIDDReader |
SAR detected images |
| CPHD | CPHDReader |
Compensated phase history |
| CRSD | CRSDReader |
Compensated received signal |
| BIOMASS L1 | BIOMASSL1Reader |
ESA BIOMASS P-band SAR (HH, HV, VH, VV) |
| Sentinel-1 IW SLC | Sentinel1SLCReader |
Per-swath (IW1/IW2/IW3), per-polarization (VV/VH/HH/HV) |
| Sentinel-2 L1C/L2A | Sentinel2Reader |
Per-band spectral data and TCI |
| TerraSAR-X / TanDEM-X | TerraSARReader |
StripMap, SpotLight, ScanSAR |
| NISAR RSLC / GSLC | NISARReader |
L-band and S-band, per-frequency/polarization |
| JPEG2000 | JP2Reader |
Wavelet-compressed imagery |
- Tiled rendering — streams image tiles on demand; handles imagery of any size without loading the full image into memory
- Dual-pane view — side-by-side comparison with synchronized pan/zoom; automatic split when opening multi-band or multi-polarization data
- SAR complex remap — automatic log-magnitude display for complex-valued SAR data
- Contrast / brightness / gamma — real-time adjustment via per-pane controls
- Dynamic range windowing — percentile-based or manual min/max clipping
- Colormaps — grayscale, viridis, inferno, plasma, hot
- Band / polarization selector — combo box shows named bands (e.g. HH, HV, B04) with automatic reader swap for multi-polarization SAR
- Coordinate bar — live latitude/longitude readout under the cursor (requires geolocation metadata)
- Color bar — data-range reference strip alongside each pane
- GeoJSON vector overlay — load and display vector features on top of imagery
When opening a Sentinel-1 SAFE product the viewer prompts for swath selection (IW1 / IW2 / IW3) and automatically enables burst boundary masking to zero out invalid border samples. Multi-polarization products (e.g. VV + VH) are offered as a dual-pane split.
The viewer creates geolocation models automatically from reader metadata:
| Sensor | Geolocation Method |
|---|---|
| SICD | SICD projection model |
| BIOMASS | Ground control point interpolation |
| Sentinel-1 SLC | Annotation grid interpolation |
| TerraSAR-X | Annotation grid interpolation |
| NISAR RSLC | 3-D geolocation grid interpolation (middle height layer) |
| NISAR GSLC | Affine transform (already geocoded) |
| Sentinel-2 | Affine transform from CRS metadata |
| GeoTIFF | Affine transform from CRS metadata |
Available from the Tools menu:
| Tool | Description |
|---|---|
| Orthorectify | Projects imagery onto a geographic grid. In dual mode, both panes are orthorectified to a shared output grid for geo-synced comparison. Supports SICD, BIOMASS, Sentinel-1, TerraSAR-X, NISAR, and GeoTIFF sources. |
| Combine to RGB | Builds a false-color RGB composite from any combination of available bands/polarizations (e.g. HH→R, HV→G, VV→B). Includes derived channels like HH+VV and HH−VV. |
| Action | Shortcut | Output |
|---|---|---|
| Export pane as image | Ctrl+E | Saves the current pane view as PNG, JPEG, or BMP (screen capture) |
| Export data | Ctrl+Shift+E | Saves processing results: ortho → GeoTIFF, RGB composite → TIFF / PNG / JPEG (full resolution) |
The viewer can be embedded in any PyQt6 application:
from grdk.viewers.main_window import ViewerMainWindow
window = ViewerMainWindow()
window.open_file("/path/to/image.tif") # file or directory
window.open_reader(reader, geolocation=geo) # grdl reader + geolocation
window.set_array(arr, geolocation=geo, title="…") # numpy array
window.show()The underlying display component used by the viewer and all GRDK widgets. A QGraphicsView-based interactive image display with:
- Pan (mouse drag), Zoom (scroll wheel, double-click to fit)
- Contrast/Brightness adjustment (does not modify source data)
- Dynamic range windowing (manual min/max or percentile-based)
- Gamma correction
- Colormaps (grayscale, viridis, inferno, plasma, hot)
- Band selection for multi-band imagery
- Pixel inspector (hover to read raw values)
Two variants:
TiledImageCanvas— tiled, streaming display for large imagery (used by the standalone viewer)ImageCanvas— full interactive viewer for in-memory arraysImageCanvasThumbnail— non-interactive fixed-size variant for grids and galleries
See docs/image-canvas.md for the API reference.
GRDK automatically discovers and wraps GRDL processors:
- Processor discovery: Scans
grdl.image_processingandgrdl.coregistrationfor concrete classes - Tag filtering: Processors with
__processor_tags__can be filtered by modality (SAR, PAN, MSI, ...) and category (spatial_filter, contrast_enhancement, ...) - GPU dispatch: Respects
__gpu_compatible__flags — skips futile GPU attempts for scipy-dependent processors - Progress callbacks: Forwards
progress_callbackthrough the pipeline with per-step rescaling - Exception handling: Distinguishes
GrdlErrorsubtypes from general Python errors in logging - Normalization: Optional GRDL
data_prep.Normalizerintegration in the Chipper widget
# Full suite
pytest tests/ -v
# Specific module
pytest tests/test_image_canvas.py -v
# With coverage
pytest tests/ --cov=grdk --cov-report=term-missing174+ tests across 15 test modules. Widget and Qt tests auto-skip when no display is available.
Click to expand full file listing
Execution & Catalog (from grdl-runtime)
These modules are imported by GRDK widgets from the grdl_rt package:
| Package | Key Types Used by Widgets |
|---|---|
grdl_rt.execution.discovery |
discover_processors(), get_processor_tags() |
grdl_rt.execution.gpu |
GpuBackend |
grdl_rt.execution.workflow |
WorkflowDefinition, ProcessingStep, WorkflowState |
grdl_rt.execution.dsl |
DslCompiler |
grdl_rt.execution.chip |
Chip, ChipSet, ChipLabel, PolygonRegion |
grdl_rt.execution.tags |
WorkflowTags, ProjectTags |
grdl_rt.execution.project |
GrdkProject |
grdl_rt.execution.executor |
WorkflowExecutor |
grdl_rt.catalog.database |
ArtifactCatalog |
grdl_rt.catalog.models |
Artifact, UpdateResult |
grdl_rt.catalog.resolver |
resolve_catalog_path() |
grdl_rt.catalog.updater |
ArtifactUpdateWorker |
grdl_rt.catalog.pool |
ThreadExecutorPool |
| File | Purpose |
|---|---|
main_window.py |
ViewerMainWindow — standalone viewer with menus, toolbar, ortho, RGB, export |
dual_viewer.py |
DualGeoViewer — synchronized dual-pane viewer with crop-to-overlap |
geo_viewer.py |
GeoViewer — single-pane viewer with geolocation, file opening, band info |
tiled_canvas.py |
TiledImageCanvas — streaming tiled display for large imagery |
tile_cache.py |
TileCache — async tile loading with LRU eviction |
image_canvas.py |
ImageCanvas, ImageCanvasThumbnail, DisplaySettings, normalize_array |
band_info.py |
BandInfo, get_band_info — named band metadata extraction per reader type |
coordinate_bar.py |
CoordinateBar — live lat/lon readout widget |
vector_overlay.py |
GeoJSON vector overlay on image canvases |
stack_viewer.py |
NapariStackViewer — multi-layer image viewer with polygon drawing |
chip_gallery.py |
ChipGalleryWidget — scrollable thumbnail grid with click-to-label |
polygon_tools.py |
chip_stack_at_polygons — polygon-based chip extraction |
| Widget | Name | Purpose |
|---|---|---|
ow_image_loader.py |
Image Loader | Load TIFF/NITF/HDF5 into image stack |
ow_stack_viewer.py |
Stack Viewer | Napari viewer with polygon drawing |
ow_coregister.py |
Co-Register | ORB/SIFT feature-match registration (affine/homography) |
ow_processor.py |
Processor | Single GRDL processor with tunable params |
ow_orchestrator.py |
Orchestrator | Multi-step pipeline builder |
ow_preview.py |
Preview | Real-time before/after GPU preview |
ow_chipper.py |
Chipper | Extract chips from polygon definitions |
ow_labeler.py |
Labeler | Click-to-label chip classification |
ow_project.py |
Project | Save/load GRDK project directories |
ow_publisher.py |
Publisher | Publish workflows to catalog |
| Widget | Name | Purpose |
|---|---|---|
ow_catalog_browser.py |
Catalog Browser | Search/discover artifacts |
ow_artifact_editor.py |
Artifact Editor | Edit catalog artifact metadata |
ow_workflow_manager.py |
Workflow Manager | Import/export workflow files |
ow_update_monitor.py |
Update Monitor | Check for package updates |
| File | Purpose |
|---|---|
_signals.py |
ImageStack, ChipSetSignal, ProcessingPipelineSignal, WorkflowArtifactSignal, GrdkProjectSignal |
_param_controls.py |
build_param_controls() — auto-generates Qt controls from TunableParameterSpec |
_display_controls.py |
build_display_controls() — contrast/brightness/gamma/colormap/window UI |
GRDK's GUI can be displayed on a remote machine (via SSH X11 forwarding) or from inside a Docker/Podman container. PyQt6 (Qt6) requires the X11/XCB platform plugin and associated libraries on the remote/container side, and a running X server on the host side.
Install the X11/XCB runtime libraries required by Qt6:
# Debian / Ubuntu
apt-get update && apt-get install -y \
libxcb1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 \
libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libxkbcommon0 \
libegl1 libgl1-mesa-glx libglib2.0-0 libfontconfig1 libdbus-1-3 \
x11-utils
# RHEL / Fedora / Rocky
dnf install -y \
libxcb xcb-util-cursor xcb-util-image xcb-util-keysyms \
xcb-util-renderutil xcb-util-wm libxkbcommon-x11 libxkbcommon \
mesa-libEGL mesa-libGL glib2 fontconfig dbus-libs xorg-x11-utilsSet these before launching GRDK:
# Force the XCB platform plugin (required for X11 forwarding)
export QT_QPA_PLATFORM=xcb
# Disable OpenGL for pure software rendering over X11 (avoids GLX errors)
export QT_QUICK_BACKEND=software
export LIBGL_ALWAYS_SOFTWARE=1
# If you see "Could not connect to display", verify DISPLAY is set:
echo $DISPLAY # Should show e.g. :0 or localhost:10.0From your local machine (the one with the display):
# Connect with X11 forwarding enabled
ssh -X user@remote-host
# Or for trusted forwarding (faster, less restrictive):
ssh -Y user@remote-hostOn the remote host:
# Verify DISPLAY is set (ssh -X sets it automatically)
echo $DISPLAY
# Set Qt platform and launch
export QT_QPA_PLATFORM=xcb
export QT_QUICK_BACKEND=software
# Install and run
pip install -e .
python -c "from PyQt6.QtWidgets import QApplication; print('PyQt6 OK')" # Quick test
grdk-canvas # Launch GRDK GUIHost setup (run once per session on your local machine):
# Allow local Docker containers to access the X server
xhost +local:dockerRun the container with X11 socket and DISPLAY forwarded:
docker run -it \
-e DISPLAY=$DISPLAY \
-e QT_QPA_PLATFORM=xcb \
-e QT_QUICK_BACKEND=software \
-e LIBGL_ALWAYS_SOFTWARE=1 \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
--network=host \
your-grdk-image:latest \
bashExample Dockerfile:
FROM python:3.12-slim
# Install X11/XCB runtime dependencies for Qt6
RUN apt-get update && apt-get install -y --no-install-recommends \
libxcb1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 \
libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libxkbcommon0 \
libegl1 libgl1-mesa-glx libglib2.0-0 libfontconfig1 libdbus-1-3 \
&& rm -rf /var/lib/apt/lists/*
# Set Qt environment for X11 forwarding
ENV QT_QPA_PLATFORM=xcb
ENV QT_QUICK_BACKEND=software
ENV LIBGL_ALWAYS_SOFTWARE=1
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -e .
CMD ["grdk-canvas"]Build and run:
docker build -t grdk-gui .
xhost +local:docker
docker run -it \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
--network=host \
grdk-guiPodman works similarly but uses --userns=keep-id for rootless operation:
xhost +local:
podman run -it \
-e DISPLAY=$DISPLAY \
-e QT_QPA_PLATFORM=xcb \
-e QT_QUICK_BACKEND=software \
-e LIBGL_ALWAYS_SOFTWARE=1 \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
--userns=keep-id \
--network=host \
grdk-gui| Symptom | Cause | Fix |
|---|---|---|
Could not connect to display |
DISPLAY not set or X server not reachable |
Verify echo $DISPLAY, use ssh -X, mount X11 socket |
qt.qpa.xcb: could not connect |
Missing xcb libraries or no X server | Install libxcb* packages (see Prerequisites) |
Could not load the Qt platform plugin "xcb" |
Missing Qt XCB platform dependencies | Install all libxcb* and libxkbcommon* packages |
GLX/OpenGL errors |
Hardware GL not available over X11 | Set QT_QUICK_BACKEND=software and LIBGL_ALWAYS_SOFTWARE=1 |
Authorization required |
xhost not configured | Run xhost +local:docker on host |
No protocol specified |
Container user mismatch | Use xhost +local: or pass --userns=keep-id (Podman) |
| Blank/frozen window | Compositor issue over forwarding | Try export QT_X11_NO_MITSHM=1 |
| Slow rendering | Network-bound X11 pixel transfer | Use SSH compression (ssh -XC) or consider VNC |
xhost +local:docker permits any local process running under the docker user to access your X server. For production or multi-user environments, prefer X11 cookie-based authentication:
# More secure alternative: share the X authority cookie
docker run -it \
-e DISPLAY=$DISPLAY \
-e XAUTHORITY=/tmp/.Xauthority \
-v $XAUTHORITY:/tmp/.Xauthority:ro \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
--network=host \
grdk-guiAll dependencies are defined in pyproject.toml. Keep these files synchronized:
pyproject.toml— source of truth for versions and dependenciesrequirements.txt— regenerate withpip freeze > requirements.txtafter updatingpyproject.toml.github/workflows/publish.yml— automated PyPI publication (do not edit manually)
- Update the
versionfield inpyproject.toml(semantic versioning:major.minor.patch) - Update
requirements.txtif dependencies changed:pip install -e ".[all,dev]" && pip freeze > requirements.txt - Commit both files
- Create a git tag:
git tag v0.2.0(matches version inpyproject.toml) - Push to GitHub:
git push && git push --tags - Create a GitHub Release from the tag — this triggers the publish workflow automatically
The workflow:
- Builds wheels and source distribution using
python -m build - Publishes to PyPI with OIDC authentication (secure, no API keys)
- Artifacts are available at pypi.org/p/grdk
See CLAUDE.md for detailed dependency management guidelines.
MIT License. Copyright (c) 2026 geoint.org. See LICENSE.