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=/ncrc/home2/Guoqing.Ge/pyDAmonitor

CPU times: user 11.3 ms, sys: 19.8 ms, total: 31.2 ms
Wall time: 30.3 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 7.45 s, sys: 131 ms, total: 7.58 s
Wall time: 522 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 5.3 s, sys: 2.61 s, total: 7.91 s
Wall time: 7.91 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.63 s, sys: 64.5 ms, total: 1.69 s
Wall time: 1.7 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