plot the QC filter results for satellite DA (cris-fsr#

Adapted from Haidao Lin’s script

get the pyDAmonitor_ROOT env variable#

This step is highly recommended. It is required if one want to use the DAmonitor Python package or use the MPAS/FV3 sample data or local cartopy nature_earth_data.

%%time
# autoload external python modules if they changed
%load_ext autoreload
%autoreload 2
    
import sys, os
pyDAmonitor_ROOT=os.getenv("pyDAmonitor_ROOT")
if pyDAmonitor_ROOT is None:
    print("!!! pyDAmonitor_ROOT is NOT set. Run `source ush/load_pyDAmonitor.sh`")
else:
    print(f"pyDAmonitor_ROOT={pyDAmonitor_ROOT}\n")
sys.path.insert(0, pyDAmonitor_ROOT)
pyDAmonitor_ROOT=/home/Guoqing.Ge/pyDAmonitor

CPU times: user 16.1 ms, sys: 63.9 ms, total: 80 ms
Wall time: 119 ms

import modules#

%%time
from netCDF4 import Dataset
import cartopy
cartopy.config['data_dir'] = f"{pyDAmonitor_ROOT}/data/natural_earth_data"
import cartopy.crs as ccrs
import matplotlib.ticker as mticker
import matplotlib.pyplot as plt
import numpy as np
#matplotlib.use("Agg")
%matplotlib inline

from DAmonitor.base import query_data, query_dataset, query_obj, to_dataframe
from DAmonitor.obs import obsSpace
CPU times: user 413 ms, sys: 228 ms, total: 640 ms
Wall time: 795 ms

read satellite diagnostics using the obsSpace class#

%%time
obsfile = os.path.join(pyDAmonitor_ROOT,'data/mpasjedi/jdiag_cris-fsr_n20.nc')
obs = obsSpace(obsfile)
CPU times: user 3.22 s, sys: 4.24 s, total: 7.46 s
Wall time: 7.61 s

check attributes, dimensions and the ‘bt’ data dictionary#

for attr in obs.ds.ncattrs():
    print(f'{attr}: {obs.ds.getncattr(attr)}')

obs.ds.dimensions
converter: BUFR
platformCommonName: CRIS
_ioda_layout_version: 0
sourceFiles: gdas.t00z.crisf4.tm00.bufr_d
datetimeReference: 2021-08-01T00:00:00Z
source: MTYP 021-206 CrIS FULL SPCTRL RADIANCE  (431 CHN SUBSET)
sensor: CrIS
_ioda_layout: ObsGroup
platformLongDescription: JPSS Polar System in sunsynchronous orbit
{'Channel': "<class 'netCDF4.Dimension'>": name = 'Channel', size = 431,
 'Location': "<class 'netCDF4.Dimension'>" (unlimited): name = 'Location', size = 92878}
query_data(obs.bt, meta_exclude="sensorCentralWavenumber_")
DerivedObsError, DerivedObsValue, CloudDetectMinResidualIR, NearSSTRetCheckIR, ScanEdgeRemoval, GrossCheck, Thinning, UseflagCheck, EffectiveError0, EffectiveError1, EffectiveError2, EffectiveQC0, EffectiveQC1, EffectiveQC2, sensorZenithAngle, satelliteIdentifier, sensorScanPosition, instrumentIdentifier, longitude, latitude, sensorViewAngle, scanLineNumber, solarZenithAngle, sensorChannelNumber, sensorAzimuthAngle, fieldOfViewNumber, dateTime, solarAzimuthAngle, ObsBias0, ObsBias1, ObsBias2, constantPredictor, emissivityJacobianPredictor, hofx0, hofx1, hofx2, innov1, lapseRatePredictor, lapseRate_order_2Predictor, oman, ombg, sensorScanAnglePredictor, sensorScanAngle_order_2Predictor, sensorScanAngle_order_3Predictor, sensorScanAngle_order_4Predictor, Channel, Location
obs.bt.CloudDetectMinResidualIR.compressed()
array([1, 1, 1, ..., 1, 1, 1], shape=(304246,), dtype=int8)

find channels with valid ombg masked by CloudDetectMinResidualIR==1#

MAX_LINES = 20
knt = 0
for iChan, chanID in enumerate(obs.bt.Channel):
    mask = obs.bt.CloudDetectMinResidualIR[:, iChan] == 1
    if obs.bt.ombg[mask, iChan].size > 0:
        knt += 1
        if knt <= MAX_LINES:
            print(iChan, chanID, obs.bt.ombg[mask, iChan].size)  #  250 648 1083
23 59 2
25 61 7
26 62 16
27 63 7
28 64 27
29 65 23
30 66 39
31 67 56
32 68 43
33 69 87
34 70 87
35 71 100
36 72 129
37 73 115
38 74 148
39 75 150
40 76 148
41 77 184
42 78 167
43 79 190

Define the sat_obs_map(..) function#

def sat_obs_map(obs,iChan, area, colors, qcfilters):
    fig = plt.figure(figsize=(10, 10))
    ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=0))
    
    # Plot grid lines
    # ----------------
    gl = ax.gridlines(crs=ccrs.PlateCarree(central_longitude=0), draw_labels=True,
                      linewidth=1, color='gray', alpha=0.5, linestyle='-')
    gl.top_labels = False
    gl.xlabel_style = {'size': 5, 'color': 'black'}
    gl.ylabel_style = {'size': 5, 'color': 'black'}
    # gl.xlocator = mticker.FixedLocator(
        # [-180, -135, -90, -45, 0, 45, 90, 135, 179.9])

    qcfilterList = qcfilters.split(',')
    colorList = colors.split(',')

    for qcfilter, color in zip(qcfilterList, colorList):
        mask = obs.bt[qcfilter][:, iChan] == 1
        field = obs.bt.ombg[mask, iChan]
        lon = obs.bt.longitude[mask]
        lat = obs.bt.latitude[mask]
        knt = np.ma.count(field)
        sc = ax.scatter(lon, lat, color=color, 
                        label=f"{qcfilter}, Total Count: {knt}",
                        s=2, linewidth=0, transform=ccrs.PlateCarree(), norm=None, antialiased=True)
        
    ax.set_extent(area)
    ax.coastlines()
    plt.title(f"{qcfilter}, Total Count: {knt}")
    plt.show()
iChan = 250
area = [-150, -50, 15, 55]

#
colors = "blue"
qcfilters = "CloudDetectMinResidualIR"
sat_obs_map(obs, iChan, area, colors, qcfilters)
#
colors = "blue"
qcfilters = "GrossCheck"
sat_obs_map(obs, iChan, area, colors, qcfilters)
#
# plot CloudDetectMinResidualIR and GrossCheck together
colors = "blue,red"
qcfilters = "CloudDetectMinResidualIR,GrossCheck"
qcfilters.split(',')
sat_obs_map(obs, iChan, area, colors, qcfilters)
#
colors = "blue"
qcfilters = "NearSSTRetCheckIR"
sat_obs_map(obs, iChan, area, colors, qcfilters)
#
colors = "blue"
qcfilters = "UseflagCheck"
sat_obs_map(obs, iChan, area, colors, qcfilters)
../_images/9010d1838dc50e03b4f20d1eff1bfdb5fd451d7597d78d27338a424573207ea5.png ../_images/420b0632083b994141eb113d6d17f2bc3bcb1873f31ce5bc8c9922dec5886cae.png ../_images/e9e27fe967c948b2e226b155c22040bd75e036ca092199ec8dfb11395f821e4d.png ../_images/ce961599dcae4a734d9a7a6c2ba1fe91266c8303644f36fcba58fa27d5330beb.png ../_images/262152d2fa3ce25938832b7d240659e419f16999f84df121b2e14f4acd17c1a3.png

Use a slidebar to quickly navigate through all channels#

%%time
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = 'notebook'

# 
qcfilter = "CloudDetectMinResidualIR"

data = {}
for iChan, ch in enumerate(obs.bt.Channel):
    mask = obs.bt[qcfilter][:, iChan] == 1
    field = obs.bt.ombg[mask, iChan]
    lon = obs.bt.longitude[mask]
    lat = obs.bt.latitude[mask]
    knt = np.ma.count(field)
    data[f"{ch}"] = {
        "lat": lat,
        "lon": lon,
        "knt": knt,
    }

fig = go.Figure()

# Add initial trace (first channel)
ch0 = f"{obs.bt.Channel[0]}"
fig.add_trace(go.Scattermap(
    lat=data[ch0]["lat"],
    lon=data[ch0]["lon"],
    mode='markers',
    marker=dict(size=10, color='blue'),
    name=ch0
))

# Define slider steps to replace data
steps = []
for ch in data:
    step = dict(
        method="update",
        args=[{"lat": [data[ch]["lat"]], "lon": [data[ch]["lon"]], "name": ch},
              {"title.text": f"Channel: {ch}"},],
        label=ch
    )
    steps.append(step)

sliders = [dict(active=0, pad={"t": 50}, steps=steps)]

# Layout
fig.update_layout(
    map=dict(
        center={"lat": 36.5, "lon": -118},  # adjust for your data
        zoom=4
    ),
    sliders=sliders,
    width=1000,   # pixels
    height=800,   # pixels
)

fig.show()
CPU times: user 1.59 s, sys: 160 ms, total: 1.75 s
Wall time: 1.81 s
query_dataset(obs.ds, meta_exclude="sensorCentralWavenumber")
DerivedObsError
    brightnessTemperature, radiance
DerivedObsValue
    brightnessTemperature
DiagnosticFlags
    CloudDetectMinResidualIR
        brightnessTemperature
    NearSSTRetCheckIR
        brightnessTemperature
    ScanEdgeRemoval
        brightnessTemperature
    GrossCheck
        brightnessTemperature
    Thinning
        brightnessTemperature
    UseflagCheck
        brightnessTemperature
EffectiveError0
    brightnessTemperature, radiance
EffectiveError1
    brightnessTemperature, radiance
EffectiveError2
    brightnessTemperature, radiance
EffectiveQC0
    brightnessTemperature, radiance
EffectiveQC1
    brightnessTemperature, radiance
EffectiveQC2
    brightnessTemperature, radiance
MetaData
    sensorZenithAngle, satelliteIdentifier, sensorScanPosition, instrumentIdentifier, longitude, latitude, sensorViewAngle, scanLineNumber, solarZenithAngle, sensorChannelNumber, sensorAzimuthAngle, fieldOfViewNumber, dateTime, solarAzimuthAngle
ObsBias0
    brightnessTemperature
ObsBias1
    brightnessTemperature
ObsBias2
    brightnessTemperature
ObsValue
    radiance
ObsValueAdj
    radiance
constantPredictor
    brightnessTemperature
emissivityJacobianPredictor
    brightnessTemperature
hofx0
    brightnessTemperature
hofx1
    brightnessTemperature
hofx2
    brightnessTemperature
innov1
    brightnessTemperature
lapseRatePredictor
    brightnessTemperature
lapseRate_order_2Predictor
    brightnessTemperature
oman
    brightnessTemperature
ombg
    brightnessTemperature
sensorScanAnglePredictor
    brightnessTemperature
sensorScanAngle_order_2Predictor
    brightnessTemperature
sensorScanAngle_order_3Predictor
    brightnessTemperature
sensorScanAngle_order_4Predictor
    brightnessTemperature
Channel, Location