Rendering Pipeline#
This page documents how WISER turns a loaded raster dataset into the pixels you
see on screen: how display bands are chosen, how contrast stretches are built
and applied, how results are cached, and how all of this is driven by
RasterView. It is the entry point for three closely related subsystems, each
documented in depth on its own page:
Band Chooser — selecting which dataset band(s) map to red, green, blue, or grayscale.
Stretch Builder — building and applying the per-channel contrast stretches that map raw values to display brightness.
Data Caching — the three-tier cache that avoids recomputing band data, rendered images, and histograms.
For how RasterView fits into the larger display system (panes, zoom, linked
scrolling, coordinate systems), see Viewport System. This
page covers only the data → image path.
Overview#
RasterView owns the rendering pipeline. Its core method,
update_display_image() (src/wiser/gui/rasterview.py), is the
single place where a RasterDataSet, a set of display bands, a list of
stretches, and an optional colormap are combined into the QPixmap shown on
screen.
The three inputs are held as attributes on RasterView and changed through
setters that invalidate cached state and re-render:
Attribute |
Set by |
Source subsystem |
|---|---|---|
|
|
|
|
|
|
|
|
Everything else — normalized band data, the final image, and histograms used by the Stretch Builder — flows through the shared data cache.
The Pipeline, End to End#
flowchart TD
DS["RasterDataSet"]
BANDS["_display_bands<br/>(1 or 3 indices)"]
NORM["get_band_data_normalized()<br/>float values in [0, 1]"]
JOINT{"Joint stretch?<br/>(_detect_joint_stretch)"}
PERBAND["Per-band path:<br/>make_channel_image()<br/>conditioner then stretch"]
JOINTPATH["Joint path:<br/>_render_joint_channels()<br/>apply_multi() on (H,W,3)"]
U8["per-channel uint8<br/>(0..255)"]
RGB["make_rgb_image() /<br/>make_grayscale_image()<br/>packed uint32 ARGB"]
QIMG["QImage / QPixmap<br/>(_image_pixmap)"]
CC[("Computation cache<br/>normalized bands")]
RC[("Render cache<br/>final uint32 image")]
DS --> NORM
BANDS --> NORM
NORM -.->|lookup / store| CC
NORM --> JOINT
JOINT -->|no| PERBAND
JOINT -->|yes| JOINTPATH
PERBAND --> U8
JOINTPATH --> U8
U8 --> RGB
RGB -.->|lookup / store| RC
RGB --> QIMG
Stage-by-stage#
Stage |
Responsible code |
Cache used |
|---|---|---|
Check for a fully-rendered image |
|
Render cache |
Read + normalize each band to |
|
Computation cache |
Decide per-band vs joint stretch |
|
— |
Apply stretch, scale to |
|
Joint result cache (in-view) |
Pack channels into ARGB |
|
— |
Store rendered image |
|
Render cache |
Wrap as |
|
— |
The render cache is checked first: if a uint32 image already exists for the
exact (dataset, bands, stretches, colormap) combination, all of the
intermediate work is skipped. See Data Caching for the cache
keys and an important known issue affecting whether these lookups currently
hit.
Per-band vs Joint Stretches#
Most stretches operate on one band at a time (StretchBase.apply() mutates a
2-D array in place). A few — notably the decorrelation stretch — are
joint: they must see all three display bands together. RasterView
distinguishes the two cases with _detect_joint_stretch(), which inspects each
channel’s stretch and returns a joint stretch only if every channel agrees
on the same one (otherwise it logs a warning and falls back to per-band).
Per-band path — for each dirty channel,
make_channel_image()applies the optional conditioner then the main stretch, clips to[0, 1], and scales touint8.Joint path —
_render_joint_channels()gathers all bands into one(H, W, 3)float32 buffer, applies per-band conditioners, runsapply_multi()once, then clips and scales each channel. The joint result is memoized inRasterView._joint_render_cacheso re-renders that don’t change the bands or conditioners can skip the expensive transform.
See Stretch Builder for the stretch model and the conditioner/stretch composition that this detection relies on.
How Changes Reach the Screen#
The Band Chooser and Stretch Builder are dialogs; they never touch RasterView
directly. Changes flow through ApplicationState (the state holder) and the
App class (the signal broker), then down to every RasterView showing the
affected dataset. This mirrors the broker pattern described in
Viewport System.
sequenceDiagram
participant SB as StretchBuilderDialog
participant App as App (broker)
participant AS as ApplicationState
participant RP as RasterPane
participant RV as RasterView
Note over SB,RV: Stretch changed
SB->>App: stretch_changed(ds_id, bands, stretches)
App->>AS: set_stretches(ds_id, bands, stretches)
AS->>RP: stretch_changed(ds_id, bands)
RP->>AS: get_stretches(ds_id, bands)
RP->>RV: set_stretches(stretches)
RV->>RV: update_display_image()
Note over SB,RV: Bands / colormap changed (global)
Note right of RP: BandChooserDialog returns, RasterPane emits display_bands_change
RP->>App: display_bands_change(ds_id, bands, colormap, is_global)
App->>RP: set_display_bands() on every pane
RP->>RV: set_display_bands(bands, stretches, colormap)
RV->>RV: update_display_image()
Stretch change path — StretchBuilderDialog.stretch_changed
→ App._on_stretch_changed → ApplicationState.set_stretches (stores the
stretch per (ds_id, band_index) and re-emits stretch_changed)
→ RasterPane._on_stretch_changed → RasterView.set_stretches.
Band/colormap change path — BandChooserDialog returns from exec_()
→ RasterPane._on_band_chooser emits display_bands_change
→ App._on_display_bands_change calls set_display_bands on all panes (when
global) → RasterView.set_display_bands. A non-global change is applied only to
the originating pane.
Both setters null out RasterView._joint_render_cache and call
update_display_image(), so the next paint reflects the change.
Where to Read Next#
Band Chooser —
_display_bandsand_colormap.Stretch Builder —
_stretches, theStretchBasehierarchy, conditioners, and the joint decorrelation path.Data Caching — the render, computation, and histogram caches, their keys, lifecycle, and a known issue.
Viewport System — zoom, pan, coordinate systems, and how
RasterViewsits inside the pane hierarchy.