pycwr is a Python toolkit for operational Chinese weather radar workflows.
It covers radar base-data reading, geometry, plotting, quality control,
hydrometeor classification, single-radar wind retrieval, multi-radar
compositing, and export.
- Current release:
1.0.8 - 中文说明
- API reference
- Radar network quickstart
- Read the Docs
- Test guide
- Draw quickstart
1.0.8 continues the first stable release line intended to be usable for production-style
usage and GitHub distribution.
This refresh keeps the PA reader behavior aligned while improving the
maintainability of the PA decode path and preserving the plotting colormap
compatibility update in the public release line.
Highlights:
- compatibility-focused readers for common Chinese weather radar formats
- clearer
PRDdata model for sweep inspection and downstream processing - aligned and native reflectivity access kept side by side when low-level reflectivity range is longer than Doppler range
- lighter default dependencies with plotting, QC, web viewer, and interop isolated into the optional full stack
- stronger geometry and regression coverage
- built-in single-radar wind retrieval workflows:
VAD,VVP,VWP, and gridded horizontal wind volumes - improved multi-radar compositing and reference-style CR plotting
read_autoandread_PAnow correctly recognize and build supported PA filesplot_ppi,plot_ppi_map, and the legacyGraph/GraphMapplotting paths now auto-registerpycwrcolormap names such asCN_refandCN_vel- the PA reader implementation is cleaner internally, with explicit helpers for moment decoding, field padding, and range construction
Install from PyPI:
python -m pip install pycwrInstall the optional full stack from PyPI:
python -m pip install "pycwr[full]"Base install:
python -m pip install -r requirements-core.txt
python -m pip install .Full install:
python -m pip install -r requirements-full.txt
python -m pip install ".[full]"Notes:
pycwr 1.0.8requires Python>=3.9python -m pip install pycwris the recommended path for normal users- base install is enough for readers,
PRD, geometry, interpolation, and NetCDF-style export - full install is recommended for plotting, map plotting, QC, Py-ART/xradar interop, and the web viewer
- upstream
arm_pyartandxradarcurrently require Python>=3.10, so on Python3.9the full install still covers plotting, QC, and the web viewer, but not those two optional interop stacks pandasis pinned to<3in1.0.8for release stability- source install is still useful when you are developing locally or rebuilding the Cython extension
Rebuild the extension after editing pycwr/core/RadarGridC.pyx:
python setup.py build_ext --inplaceBuild release artifacts:
python -m buildRead one radar file and inspect the returned volume:
from pycwr.io import read_auto
radar = read_auto("Z_RADR_I_Z9046_20260317065928_O_DOR_SAD_CAP_FMT.bin.bz2")
print(radar.summary())
print(radar.available_fields())
print(radar.sweep_summary()[0])Get one field from one sweep:
dBZ0 = radar.get_sweep_field(0, "dBZ")
velocity0 = radar.get_sweep_field(0, "V")Plot a PPI:
from pycwr.draw import plot_ppi
plot_ppi(radar, field="dBZ", sweep=0, show=True)Plot a mapped PPI:
from pycwr.draw import plot_ppi_map
plot_ppi_map(radar, field="dBZ", sweep=0, show=True)Extract a vertical section:
from pycwr.draw import plot_section
plot_section(radar, start=(-50, 0), end=(50, 0), field="dBZ", show=True)Generate a simple product:
import numpy as np
x = np.arange(-150_000.0, 150_001.0, 1_000.0)
y = np.arange(-150_000.0, 150_001.0, 1_000.0)
radar.add_product_CR_xy(x, y)
print(radar.product)| Module | What it is for | Recommended starting points |
|---|---|---|
pycwr.io |
Read and write radar base data | read_auto, read_WSR98D, read_SAB, read_CC, read_SC, read_PA |
pycwr.core |
Central volume object, geometry, export helpers | PRD, radar.summary(), radar.get_sweep_field() |
pycwr.draw |
Plotting and quick-look figures | plot_ppi, plot_ppi_map, plot_rhi, plot_section, plot_vvp, plot_wind_profile |
pycwr.qc |
Dual-pol QC | apply_dualpol_qc, run_dualpol_qc |
pycwr.retrieve |
Hydrometeor and wind retrieval | classify_hydrometeors, retrieve_vad, retrieve_vvp, retrieve_vwp, retrieve_wind_volume_xy, retrieve_wind_volume_lonlat |
pycwr.interp |
Multi-radar compositing | run_radar_network_3d |
pycwr.GraphicalInterface |
Local web viewer | create_app, launch |
All readers return pycwr.core.NRadar.PRD.
The most important parts of PRD are:
fields: onexarray.Datasetper sweepscan_info: site and scan metadataextended_fields: native sidecar fields when aligned and native ranges differproduct: computed product dataset
Useful inspection helpers:
summary(): compact full-volume summaryavailable_fields(sweep=None, range_mode="aligned")sweep_summary()get_sweep_field(sweep, field_name, range_mode="aligned", sort_by_azimuth=False)get_native_sweep_field(sweep, field_name)ordered_az(inplace=False)
For some low sweeps, reflectivity may exist in two forms:
- aligned: historical shared grid used by old processing chains
- native: original reflectivity range before Doppler-driven truncation
Use:
range_mode="aligned"for historical compatibilityrange_mode="native"when full low-level reflectivity coverage matters
Example:
aligned = radar.get_sweep_field(0, "dBZ", range_mode="aligned")
native = radar.get_sweep_field(0, "dBZ", range_mode="native")Recommended public plotting APIs:
from pycwr.draw import (
plot,
plot_ppi,
plot_ppi_map,
plot_rhi,
plot_section,
plot_section_lonlat,
plot_vvp,
plot_wind_profile,
)These functions return an EasyPlotResult with fig, ax, and artist.
pycwr.draw chooses a default colormap from the plotted field when you leave
cmap=None.
dBZuses the reflectivity palette such asCN_refVuses the velocity palette such asCN_velHCLuses a discrete hydrometeor classification palette- unknown fields fall back to
viridis
This default strategy is shared by plot_ppi, plot_ppi_map, plot_rhi,
plot_section, and the legacy Graph / GraphMap classes.
Recommended usage:
from pycwr.draw import plot_ppi, plot_ppi_map
plot_ppi(radar, field="dBZ", sweep=0, show=True)
plot_ppi_map(radar, field="dBZ", sweep=0, show=True)Explicit colormap override:
plot_ppi(radar, field="dBZ", sweep=0, cmap="CN_ref", cmap_bins=16, show=True)
plot_ppi_map(radar, field="V", sweep=0, cmap="CN_vel", min_max=(-27.0, 27.0), show=True)Legacy class-style usage is also supported:
from pycwr.draw.RadarPlot import GraphMap
from cartopy import crs as ccrs
display = GraphMap(radar, ccrs.PlateCarree())
display.plot_ppi_map(ax, 0, "dBZ", cmap="CN_ref")Notes:
CN_ref,CN_vel, and the otherpycwrcolormap names are registered automatically when plotting starts. You no longer need to "warm up" the plotting module first.- If a field is missing in the radar file, plotting raises a field-not-found
error. For example, some files may contain
dBZbut notV. - If you need the raw colormap registry for custom matplotlib work, you can
explicitly import
pycwr.draw.colormap.
Common PRD product methods:
add_product_CR_xyadd_product_CAPPI_xyadd_product_CAPPI_3d_xyadd_product_VIL_xyadd_product_ET_xyadd_product_CR_lonlatadd_product_CAPPI_lonlatadd_product_VIL_lonlatadd_product_ET_lonlatadd_product_VWP
Example:
radar.add_product_CAPPI_xy(x, y, 3000.0)
radar.add_product_VIL_xy(x, y, [1000.0, 2000.0, 3000.0])from pycwr.qc import apply_dualpol_qc
qc_radar = apply_dualpol_qc(radar, inplace=False, clear_air_mode="mask")Common corrected fields include Zc, ZDRc, PhiDPc, KDPc, and mask fields
such as QC_MASK and CLEAR_AIR_MASK when enabled.
hcl_radar = radar.classify_hydrometeors(
inplace=False,
band="C",
profile_height=[0.0, 2000.0, 4000.0, 8000.0],
profile_temperature=[24.0, 12.0, 2.0, -16.0],
confidence_field="HCL_CONF",
)You can also classify directly from arrays with
pycwr.retrieve.classify_hydrometeors(...).
pycwr ships four single-radar wind workflows:
retrieve_vad: ring-wise harmonic fit on one or more sweepsretrieve_vvp: local least-squares horizontal wind retrieval on one sweepretrieve_vwp: vertical wind profile built from multiple VAD layersretrieve_wind_volume_xy/retrieve_wind_volume_lonlat: fixed-height gridded horizontal wind volume built from multiple VVP sweeps
Quick example:
vad = radar.retrieve_vad(sweeps=[0, 1, 2], max_range_km=40.0, gate_step=4)
vvp = radar.retrieve_vvp(0, max_range_km=20.0, az_num=91, bin_num=5)
vwp = radar.retrieve_vwp(sweeps=[0, 1, 2], max_range_km=40.0, height_step=500.0)
wind = radar.retrieve_wind_volume_xy(
XRange=np.arange(-20_000.0, 20_001.0, 10_000.0),
YRange=np.arange(-20_000.0, 20_001.0, 10_000.0),
level_heights=np.array([500.0, 1000.0, 1500.0]),
sweeps=[0, 1, 2],
max_range_km=30.0,
)The stored profile product path is:
radar.add_product_VWP(sweeps=[0, 1, 2], max_range_km=40.0, height_step=500.0)Store the gridded horizontal wind volume in radar.product:
radar.add_product_WIND_VOLUME_xy(
XRange=np.arange(-20_000.0, 20_001.0, 10_000.0),
YRange=np.arange(-20_000.0, 20_001.0, 10_000.0),
level_heights=np.array([500.0, 1000.0, 1500.0]),
sweeps=[0, 1, 2],
)retrieve_wind_volume_xy and retrieve_wind_volume_lonlat return a single-radar
fixed-height horizontal wind volume.
Treat it as:
- gridded
u/vwind on multiple height levels - one wind profile for each horizontal grid point
- a robust diagnostic product built from Doppler radial velocity
Do not treat it as:
- a full dynamic
u/v/wretrieval - a dual-Doppler or multi-radar variational wind analysis
- a substitute for vertical motion retrieval in convective dynamics studies
The operational logic is:
- For each requested sweep,
pycwrselects the velocity field, preferring corrected velocity when available. - A local VVP is solved on each sweep with a moving azimuth-range window.
- Each sweep is summarized and, when
sweeps=Noneorsweeps="auto", the strongest sweeps are selected automatically. - The valid sweep-level VVP samples are interpolated horizontally onto the target grid with inverse-distance weighting.
- Fixed-height wind values are reconstructed from the selected sweeps with local vertical aggregation or short-gap linear interpolation.
- Diagnostics such as fit residual, support counts, and heuristic quality score are stored with the result.
This makes the product more stable on real radar volumes with missing velocity, weak echo regions, or sweep-to-sweep range differences.
Use:
radar.retrieve_wind_volume_xy(...)for Cartesianx/y/zradar.retrieve_wind_volume_lonlat(...)for regular lon/lat gridsradar.add_product_WIND_VOLUME_xy(...)orradar.add_product_WIND_VOLUME_lonlat(...)to store the result inradar.product
Recommended defaults for production-style usage:
- use
sweeps=Noneto enable auto sweep selection - use
sweeps="all"only when you explicitly want every available sweep - set a realistic
max_range_kminstead of using the whole radar volume - increase
workerson multi-core machines when the target grid is not tiny
Example with explicit operational settings:
wind = radar.retrieve_wind_volume_xy(
XRange=np.arange(-60_000.0, 60_001.0, 5_000.0),
YRange=np.arange(-60_000.0, 60_001.0, 5_000.0),
level_heights=np.arange(500.0, 3_500.0, 500.0),
sweeps=None,
max_range_km=60.0,
az_num=31,
bin_num=5,
azimuth_step=12,
range_step=6,
horizontal_radius_m=8_000.0,
max_horizontal_radius_m=14_000.0,
horizontal_min_neighbors=3,
vertical_tolerance_m=400.0,
max_vertical_gap_m=1_200.0,
workers=4,
)XRange,YRange,level_heights: target output grid in metersXLon,YLat: target output grid in degrees for lon/lat modesweeps:Noneor"auto"enables auto selection;"all"keeps all sweeps; a list such as[0, 1, 2]forces explicit sweepsmax_range_km: strongest first-order control for retrieval stabilityaz_num,bin_num: local VVP window sizeazimuth_step,range_step: output thinning of the local sweep VVPhorizontal_radius_m: horizontal radius used when mapping sweep VVP samples to the target gridmax_horizontal_radius_m: optional expanded search radius when local support is sparsevertical_tolerance_m: local height matching tolerancemax_vertical_gap_m: maximum allowed vertical interpolation gapworkers: process-based parallelism for sweep-level and grid-level work
Practical tuning rules:
- use a wider
az_numwhen velocity is sparse or noisy - keep
bin_nummodest; too small is noisy and too large oversmooths - do not push
max_range_kmfarther than the range where velocity remains reasonably continuous - on sparse cases, it is better to slightly increase search radius than to force too many poor sweeps
The returned dataset includes:
u,v: eastward and northward wind componentswind_speed,wind_directionfit_rmse: local retrieval residualvalid_count: effective sample countsource_sweep_count: number of sweeps contributing to the voxelneighbor_count: horizontal support counteffective_vertical_support: number of vertically supporting sweep samplessweep_spread_m: vertical spread of supporting sweepsquality_score: heuristic score from0to100quality_flag:0/1/2/3for invalid, low-confidence, usable, and high-confidence voxels
Important attrs include:
selection_moderequested_sweepsselected_sweepsrejected_sweepsrejected_sweep_reasonsworkers_usedparallel_mode
These attrs are often more informative than the plot itself when diagnosing why a real case contains gaps.
If a real case has large missing areas, that does not automatically mean the algorithm failed.
Common causes are:
- no precipitation or very weak echo, so there is no valid Doppler velocity
- poor azimuth coverage in the local window
- sweep geometry that does not support the requested height well
- too aggressive a range limit or too small a horizontal search radius
When checking a case, inspect:
quality_scoreandquality_flagfit_rmsevalid_countsource_sweep_countselected_sweepsandrejected_sweep_reasons
The workers argument uses process-based parallelism, not Python threads.
In practice:
- small grids may see little benefit
- moderate and large grids usually benefit from
workers=2orworkers=4 - very large worker counts may stop scaling once process overhead dominates
For reproducible validation, compare a serial run and a parallel run on the
same case and confirm that u, v, and quality_flag match.
Common export helpers:
radar.to_wsr98d(...)radar.to_nexrad_level2_msg31(...)radar.to_nexrad_level2_msg1(...)radar.to_pyart_radar(...)radar.to_xradar(...)radar.to_cfgridded_netcdf(...)
Use these when you need to move pycwr data into Py-ART, xradar, or other
NetCDF-style workflows.
from pycwr.interp import run_radar_network_3dThis entry point builds 3D network products on a regular lon/lat grid and can write NetCDF directly. It is the recommended high-level interface for network CR and CAPPI workflows.
from pycwr.GraphicalInterface import create_app, launchOr use the script:
python scripts/LaunchGUI.pyThe viewer is local-only by design and requires a token for API access.
- API reference: detailed public interface notes
- Radar network quickstart: beginner-friendly multi-radar workflow
- Test guide: runnable examples by feature
- Draw quickstart: plotting-focused entry points
- Read the Docs: hosted documentation site
- Web viewer: launch it locally with
python scripts/LaunchGUI.py
For 1.0.8, the most important user-visible behavior rules are:
- radar reading returns one stable
PRDobject across supported formats - low-level reflectivity can now be queried explicitly in aligned or native mode
- QC and hydrometeor classification can write corrected fields back into
PRD - single-radar wind retrieval is now part of the public API, including gridded horizontal wind volumes
- multi-radar compositing has a documented high-level workflow
- packaging is split into base and full dependency sets for lower installation friction
- PA samples matching the supported format markers are routed through
read_PAinstead of being misidentified asWSR98D - default plotting now resolves
pycwrcolormap names consistently across both the easy plotting API and legacy class-style plotting