methane_super_emitters.utils

  1import glob
  2import os
  3import random
  4import shutil
  5import numpy as np
  6import netCDF4
  7import datetime
  8import geedim
  9import logging
 10import pathlib
 11import rasterio
 12from rasterio.plot import reshape_as_image
 13from rasterio.transform import from_origin
 14from PIL import Image
 15from dateutil.relativedelta import relativedelta
 16
 17
 18def s2_download(npz_file, output_dir):
 19    """Download a Sentinel-2 image corresponding to a TROPOMI patch.
 20
 21    Parameters
 22    ----------
 23    npz_file: str
 24        A patch from a TROPOMI file used for training or inference in our model.
 25    output_dir: str
 26        Output directory to store the Sentinel-2 data.
 27    """
 28    geedim.Initialize()
 29    data = np.load(npz_file, allow_pickle=True)
 30    lat = data["lat_bounds"]
 31    lon = data["lon_bounds"]
 32    lat_min = float(lat.min())
 33    lat_max = float(lat.max())
 34    lon_min = float(lon.min())
 35    lon_max = float(lon.max())
 36    polygon = {
 37        "type": "Polygon",
 38        "coordinates": [
 39            [
 40                [lat_min, lon_min],
 41                [lat_min, lon_max],
 42                [lat_max, lon_max],
 43                [lat_max, lon_min],
 44                [lat_min, lon_min],
 45            ]
 46        ],
 47    }
 48    coll = geedim.MaskedCollection.from_name("COPERNICUS/S2_HARMONIZED")
 49    date = min(data["time"])
 50    start_date = date - relativedelta(weeks=2)
 51    end_date = date + relativedelta(weeks=2)
 52    try:
 53        coll = coll.search(
 54            start_date=start_date,
 55            end_date=end_date,
 56            region=polygon,
 57            cloudless_portion=0.5,
 58        )
 59        comp_im = coll.composite(method="mosaic", region=polygon)
 60        stem = pathlib.Path(npz_file).stem
 61        output_file = os.path.join(output_dir, f"{stem}.tif")
 62        comp_im.download(
 63            output_file,
 64            region=polygon,
 65            crs="EPSG:4326",
 66            scale=20,
 67            overwrite=True,
 68            bands=["B4", "B3", "B2"],
 69        )
 70    except ValueError:
 71        logging.warning(
 72            f"No S2 data find matching the query constructed from {npz_file}"
 73        )
 74
 75
 76def s2_preview_png(tiff_file, output_dir):
 77    """Create a PNG preview of a S2 GeoTIFF file.
 78
 79    Parameters
 80    ----------
 81    tiff_file: str
 82        A GeoTIFF with the Sentinel-2 scene.
 83    output_dir: str
 84        Output directory for the result.
 85    """
 86    stem = pathlib.Path(tiff_file).stem
 87    with rasterio.open(tiff_file, "r") as src:
 88        rgb = src.read([1, 2, 3])
 89        rgb = (255 * (rgb / np.max(rgb))).astype(np.uint8)
 90        rgb_image = reshape_as_image(rgb)
 91        Image.fromarray(rgb_image).save(os.path.join(output_dir, f"{stem}.png"))
 92
 93
 94def npz_to_geotiff(npz_file, output_dir):
 95    """Store methane concentrations in a GeoTIFF for preview.
 96
 97    Parameters
 98    ----------
 99    npz_file: str
100        An NPZ file as used for training and inference.
101    output_dir: str
102        Output directory for the result.
103    """
104    stem = pathlib.Path(npz_file).stem
105    data = np.load(npz_file)
106    ch4 = data["methane"]
107    mask = data["mask"]
108    lat = data["lat_bounds"]
109    lon = data["lon_bounds"]
110    lat_res = (lat.max() - lat.min()) / lat.shape[0]
111    lon_res = (lon.max() - lon.min()) / lat.shape[1]
112    transform = from_origin(lon.min(), lat.max(), lon_res, lat_res)
113    ch4[mask] = -9999
114    with rasterio.open(
115        os.path.join(output_dir, f"{stem}_ch4.tif"),
116        "w",
117        driver="GTiff",
118        height=ch4.shape[0],
119        width=ch4.shape[1],
120        count=1,
121        dtype=np.float32,
122        crs="EPSG:4326",
123        transform=transform,
124        nodata=-9999,
125    ) as dst:
126        dst.write(ch4, 1)
127
128
129def sample_files(glob_pattern, output_dir, n):
130    """A small utility function to sample files from a directory at random.
131
132    Parameters
133    ----------
134    glob_pattern: str
135        A string of the form /path/to/*.npz or so. To select files from.
136    output_dir: str
137        A directory name into which to copy files.
138    n: int
139        How many files to take at random.
140    """
141    for path in random.sample(list(glob.glob(glob_pattern)), k=n):
142        shutil.copy(path, output_dir)
143
144
145def destripe(fd):
146    """A destriping algorithm for TROPOMI data.
147
148    Parameters
149    ----------
150    fd: a NetCDF4 file handler
151
152    Returns
153    -------
154    A numpy array with destriped methane ppm amounts.
155    """
156    ch4 = fd["/PRODUCT/methane_mixing_ratio"][:]
157    ch4corr = fd["/PRODUCT/methane_mixing_ratio_bias_corrected"][:]
158    ch4corr[ch4corr > 1e20] = np.nan
159    ch4corrdestrip = ch4corr.copy() * np.nan
160    # get the number of rows
161    n = ch4corr.shape[1]
162    # get the number of columns
163    m = ch4corr.shape[2]
164    back = np.zeros((n, m)) * np.nan
165    for i in range(m):
166        # define half window size
167        ws = 7
168        if i < ws:
169            st = 0
170            sp = i + ws
171        elif m - i < ws:
172            st = i - ws
173            sp = m - 1
174        else:
175            st = i - ws
176            sp = i + ws
177        back[:, i] = np.nanmedian(ch4corr[0, :, st:sp], axis=1)
178    this = ch4corr[0, :, :] - back
179    stripes = np.zeros((n, m)) * np.nan
180    for j in range(n):
181        ws = 60
182        if j < ws:
183            st = 0
184            sp = j + ws
185        elif n - j < ws:
186            st = j - ws
187            sp = n - 1
188        else:
189            st = j - ws
190            sp = j + ws
191        stripes[j, :] = np.nanmedian(this[st:sp, :], axis=0)
192    ch4corrdestrip[0, :, :] = ch4corr[0, :, :] - stripes
193    return ch4corrdestrip
194
195
196def parse_date(date_str, time_str):
197    """Parse date from a simple string format.
198
199    Parameters
200    ----------
201    date_str: str
202        Date of the form YYYYMMDD
203    time_str: str
204        Time of the form HH:MM:SS
205
206    Returns
207    -------
208    datetime.datetime
209    """
210    return datetime.datetime.strptime(date_str + time_str, "%Y%m%d%H:%M:%S")
211
212
213def patch_generator(file_path):
214    """A generator for 32x32 pixel patches with 50% overlap.
215
216    Parameters
217    ----------
218    file_path: str
219        A path to a TROPOMI netCDF4 file.
220
221    Yields
222    ------
223    dict
224        A dictionary with patch arrays.
225    """
226    with netCDF4.Dataset(file_path, "r") as fd:
227        destriped = destripe(fd)
228        rows = destriped.shape[1]
229        cols = destriped.shape[2]
230        for row in range(0, rows, 16):
231            for col in range(0, cols, 16):
232                if row + 32 < rows and col + 32 < cols:
233                    methane_window = destriped[0][row : row + 32][:, col : col + 32]
234                    original = fd["PRODUCT/methane_mixing_ratio_bias_corrected"][:][0][
235                        row : row + 32
236                    ][:, col : col + 32]
237                    lat_window = fd["PRODUCT/latitude"][:][0][row : row + 32][
238                        :, col : col + 32
239                    ]
240                    lon_window = fd["PRODUCT/longitude"][:][0][row : row + 32][
241                        :, col : col + 32
242                    ]
243                    lat_bounds_window = fd[
244                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/latitude_bounds"
245                    ][:][0][row : row + 32][:, col : col + 32]
246                    lon_bounds_window = fd[
247                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/longitude_bounds"
248                    ][:][0][row : row + 32][:, col : col + 32]
249                    qa_window = fd["PRODUCT/qa_value"][:][0][row : row + 32][
250                        :, col : col + 32
251                    ]
252                    u10_window = fd["PRODUCT/SUPPORT_DATA/INPUT_DATA/eastward_wind"][:][
253                        0
254                    ][row : row + 32][:, col : col + 32]
255                    v10_window = fd["PRODUCT/SUPPORT_DATA/INPUT_DATA/northward_wind"][
256                        :
257                    ][0][row : row + 32][:, col : col + 32]
258                    sza_window = fd[
259                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/solar_zenith_angle"
260                    ][:][0][row : row + 32][:, col : col + 32]
261                    vza_window = fd[
262                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/viewing_zenith_angle"
263                    ][:][0][row : row + 32][:, col : col + 32]
264                    saa_window = fd[
265                        "/PRODUCT/SUPPORT_DATA/GEOLOCATIONS/solar_azimuth_angle"
266                    ][:][0][row : row + 32][:, col : col + 32]
267                    vaa_window = fd[
268                        "/PRODUCT/SUPPORT_DATA/GEOLOCATIONS/viewing_azimuth_angle"
269                    ][:][0][row : row + 32][:, col : col + 32]
270                    f_delta = saa_window - vaa_window
271                    raa_window = np.where(
272                        f_delta < 0, np.abs(f_delta + 180.0), np.abs(f_delta - 180.0)
273                    )
274                    raa_window = abs(180.0 - raa_window)
275                    scattering_angle = -np.cos(np.radians(sza_window)) * np.cos(
276                        np.radians(vza_window)
277                    ) + np.sin(np.radians(sza_window)) * np.sin(
278                        np.radians(vza_window)
279                    ) * np.cos(
280                        np.radians(raa_window)
281                    )
282                    sa_std_window = fd[
283                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/surface_altitude_precision"
284                    ][:][0][row : row + 32][:, col : col + 32]
285                    cloud_fraction_window = fd[
286                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/cloud_fraction_VIIRS_SWIR_IFOV"
287                    ][:][0][row : row + 32][:, col : col + 32]
288                    cirrus_reflectance_window = fd[
289                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/reflectance_cirrus_VIIRS_SWIR"
290                    ][:][0][row : row + 32][:, col : col + 32]
291                    methane_ratio_std_window = fd[
292                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/methane_ratio_weak_strong_standard_deviation"
293                    ][:][0][row : row + 32][:, col : col + 32]
294                    methane_precision_window = fd[
295                        "PRODUCT/methane_mixing_ratio_precision"
296                    ][:][0][row : row + 32][:, col : col + 32]
297                    surface_albedo_window = fd[
298                        "PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/surface_albedo_SWIR"
299                    ][:][0][row : row + 32][:, col : col + 32]
300                    surface_albedo_precision_window = fd[
301                        "PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/surface_albedo_SWIR_precision"
302                    ][:][0][row : row + 32][:, col : col + 32]
303                    aerosol_optical_thickness_window = fd[
304                        "PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/aerosol_optical_thickness_SWIR"
305                    ][:][0][row : row + 32][:, col : col + 32]
306                    times = fd["PRODUCT/time_utc"][:][0][row : row + 32]
307                    try:
308                        parsed_time = [
309                            datetime.datetime.fromisoformat(time_str[:19])
310                            for time_str in times
311                        ]
312                    except ValueError:
313                        continue
314                    if original.mask.sum() < 0.2 * 32 * 32:
315                        yield {
316                            "methane": methane_window,
317                            "lat": lat_window,
318                            "lon": lon_window,
319                            "lat_bounds": lat_bounds_window,
320                            "lon_bounds": lon_bounds_window,
321                            "qa": qa_window,
322                            "time": parsed_time,
323                            "mask": original.mask,
324                            "non_destriped": original,
325                            "u10": u10_window,
326                            "v10": v10_window,
327                            "sza": np.cos(np.radians(sza_window)),
328                            "vza": np.cos(np.radians(vza_window)),
329                            "scattering_angle": scattering_angle,
330                            "sa_std": sa_std_window,
331                            "cloud_fraction": cloud_fraction_window,
332                            "cirrus_reflectance": cirrus_reflectance_window,
333                            "methane_ratio_std": methane_ratio_std_window,
334                            "methane_precision": methane_precision_window,
335                            "surface_albedo": surface_albedo_window,
336                            "surface_albedo_precision": surface_albedo_precision_window,
337                            "aerosol_optical_thickness": aerosol_optical_thickness_window,
338                        }
def s2_download(npz_file, output_dir):
19def s2_download(npz_file, output_dir):
20    """Download a Sentinel-2 image corresponding to a TROPOMI patch.
21
22    Parameters
23    ----------
24    npz_file: str
25        A patch from a TROPOMI file used for training or inference in our model.
26    output_dir: str
27        Output directory to store the Sentinel-2 data.
28    """
29    geedim.Initialize()
30    data = np.load(npz_file, allow_pickle=True)
31    lat = data["lat_bounds"]
32    lon = data["lon_bounds"]
33    lat_min = float(lat.min())
34    lat_max = float(lat.max())
35    lon_min = float(lon.min())
36    lon_max = float(lon.max())
37    polygon = {
38        "type": "Polygon",
39        "coordinates": [
40            [
41                [lat_min, lon_min],
42                [lat_min, lon_max],
43                [lat_max, lon_max],
44                [lat_max, lon_min],
45                [lat_min, lon_min],
46            ]
47        ],
48    }
49    coll = geedim.MaskedCollection.from_name("COPERNICUS/S2_HARMONIZED")
50    date = min(data["time"])
51    start_date = date - relativedelta(weeks=2)
52    end_date = date + relativedelta(weeks=2)
53    try:
54        coll = coll.search(
55            start_date=start_date,
56            end_date=end_date,
57            region=polygon,
58            cloudless_portion=0.5,
59        )
60        comp_im = coll.composite(method="mosaic", region=polygon)
61        stem = pathlib.Path(npz_file).stem
62        output_file = os.path.join(output_dir, f"{stem}.tif")
63        comp_im.download(
64            output_file,
65            region=polygon,
66            crs="EPSG:4326",
67            scale=20,
68            overwrite=True,
69            bands=["B4", "B3", "B2"],
70        )
71    except ValueError:
72        logging.warning(
73            f"No S2 data find matching the query constructed from {npz_file}"
74        )

Download a Sentinel-2 image corresponding to a TROPOMI patch.

Parameters
  • npz_file (str): A patch from a TROPOMI file used for training or inference in our model.
  • output_dir (str): Output directory to store the Sentinel-2 data.
def s2_preview_png(tiff_file, output_dir):
77def s2_preview_png(tiff_file, output_dir):
78    """Create a PNG preview of a S2 GeoTIFF file.
79
80    Parameters
81    ----------
82    tiff_file: str
83        A GeoTIFF with the Sentinel-2 scene.
84    output_dir: str
85        Output directory for the result.
86    """
87    stem = pathlib.Path(tiff_file).stem
88    with rasterio.open(tiff_file, "r") as src:
89        rgb = src.read([1, 2, 3])
90        rgb = (255 * (rgb / np.max(rgb))).astype(np.uint8)
91        rgb_image = reshape_as_image(rgb)
92        Image.fromarray(rgb_image).save(os.path.join(output_dir, f"{stem}.png"))

Create a PNG preview of a S2 GeoTIFF file.

Parameters
  • tiff_file (str): A GeoTIFF with the Sentinel-2 scene.
  • output_dir (str): Output directory for the result.
def npz_to_geotiff(npz_file, output_dir):
 95def npz_to_geotiff(npz_file, output_dir):
 96    """Store methane concentrations in a GeoTIFF for preview.
 97
 98    Parameters
 99    ----------
100    npz_file: str
101        An NPZ file as used for training and inference.
102    output_dir: str
103        Output directory for the result.
104    """
105    stem = pathlib.Path(npz_file).stem
106    data = np.load(npz_file)
107    ch4 = data["methane"]
108    mask = data["mask"]
109    lat = data["lat_bounds"]
110    lon = data["lon_bounds"]
111    lat_res = (lat.max() - lat.min()) / lat.shape[0]
112    lon_res = (lon.max() - lon.min()) / lat.shape[1]
113    transform = from_origin(lon.min(), lat.max(), lon_res, lat_res)
114    ch4[mask] = -9999
115    with rasterio.open(
116        os.path.join(output_dir, f"{stem}_ch4.tif"),
117        "w",
118        driver="GTiff",
119        height=ch4.shape[0],
120        width=ch4.shape[1],
121        count=1,
122        dtype=np.float32,
123        crs="EPSG:4326",
124        transform=transform,
125        nodata=-9999,
126    ) as dst:
127        dst.write(ch4, 1)

Store methane concentrations in a GeoTIFF for preview.

Parameters
  • npz_file (str): An NPZ file as used for training and inference.
  • output_dir (str): Output directory for the result.
def sample_files(glob_pattern, output_dir, n):
130def sample_files(glob_pattern, output_dir, n):
131    """A small utility function to sample files from a directory at random.
132
133    Parameters
134    ----------
135    glob_pattern: str
136        A string of the form /path/to/*.npz or so. To select files from.
137    output_dir: str
138        A directory name into which to copy files.
139    n: int
140        How many files to take at random.
141    """
142    for path in random.sample(list(glob.glob(glob_pattern)), k=n):
143        shutil.copy(path, output_dir)

A small utility function to sample files from a directory at random.

Parameters
  • glob_pattern (str): A string of the form /path/to/*.npz or so. To select files from.
  • output_dir (str): A directory name into which to copy files.
  • n (int): How many files to take at random.
def destripe(fd):
146def destripe(fd):
147    """A destriping algorithm for TROPOMI data.
148
149    Parameters
150    ----------
151    fd: a NetCDF4 file handler
152
153    Returns
154    -------
155    A numpy array with destriped methane ppm amounts.
156    """
157    ch4 = fd["/PRODUCT/methane_mixing_ratio"][:]
158    ch4corr = fd["/PRODUCT/methane_mixing_ratio_bias_corrected"][:]
159    ch4corr[ch4corr > 1e20] = np.nan
160    ch4corrdestrip = ch4corr.copy() * np.nan
161    # get the number of rows
162    n = ch4corr.shape[1]
163    # get the number of columns
164    m = ch4corr.shape[2]
165    back = np.zeros((n, m)) * np.nan
166    for i in range(m):
167        # define half window size
168        ws = 7
169        if i < ws:
170            st = 0
171            sp = i + ws
172        elif m - i < ws:
173            st = i - ws
174            sp = m - 1
175        else:
176            st = i - ws
177            sp = i + ws
178        back[:, i] = np.nanmedian(ch4corr[0, :, st:sp], axis=1)
179    this = ch4corr[0, :, :] - back
180    stripes = np.zeros((n, m)) * np.nan
181    for j in range(n):
182        ws = 60
183        if j < ws:
184            st = 0
185            sp = j + ws
186        elif n - j < ws:
187            st = j - ws
188            sp = n - 1
189        else:
190            st = j - ws
191            sp = j + ws
192        stripes[j, :] = np.nanmedian(this[st:sp, :], axis=0)
193    ch4corrdestrip[0, :, :] = ch4corr[0, :, :] - stripes
194    return ch4corrdestrip

A destriping algorithm for TROPOMI data.

Parameters
  • fd (a NetCDF4 file handler):
Returns
  • A numpy array with destriped methane ppm amounts.
def parse_date(date_str, time_str):
197def parse_date(date_str, time_str):
198    """Parse date from a simple string format.
199
200    Parameters
201    ----------
202    date_str: str
203        Date of the form YYYYMMDD
204    time_str: str
205        Time of the form HH:MM:SS
206
207    Returns
208    -------
209    datetime.datetime
210    """
211    return datetime.datetime.strptime(date_str + time_str, "%Y%m%d%H:%M:%S")

Parse date from a simple string format.

Parameters
  • date_str (str): Date of the form YYYYMMDD
  • time_str (str): Time of the form HH:MM:SS
Returns
  • datetime.datetime
def patch_generator(file_path):
214def patch_generator(file_path):
215    """A generator for 32x32 pixel patches with 50% overlap.
216
217    Parameters
218    ----------
219    file_path: str
220        A path to a TROPOMI netCDF4 file.
221
222    Yields
223    ------
224    dict
225        A dictionary with patch arrays.
226    """
227    with netCDF4.Dataset(file_path, "r") as fd:
228        destriped = destripe(fd)
229        rows = destriped.shape[1]
230        cols = destriped.shape[2]
231        for row in range(0, rows, 16):
232            for col in range(0, cols, 16):
233                if row + 32 < rows and col + 32 < cols:
234                    methane_window = destriped[0][row : row + 32][:, col : col + 32]
235                    original = fd["PRODUCT/methane_mixing_ratio_bias_corrected"][:][0][
236                        row : row + 32
237                    ][:, col : col + 32]
238                    lat_window = fd["PRODUCT/latitude"][:][0][row : row + 32][
239                        :, col : col + 32
240                    ]
241                    lon_window = fd["PRODUCT/longitude"][:][0][row : row + 32][
242                        :, col : col + 32
243                    ]
244                    lat_bounds_window = fd[
245                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/latitude_bounds"
246                    ][:][0][row : row + 32][:, col : col + 32]
247                    lon_bounds_window = fd[
248                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/longitude_bounds"
249                    ][:][0][row : row + 32][:, col : col + 32]
250                    qa_window = fd["PRODUCT/qa_value"][:][0][row : row + 32][
251                        :, col : col + 32
252                    ]
253                    u10_window = fd["PRODUCT/SUPPORT_DATA/INPUT_DATA/eastward_wind"][:][
254                        0
255                    ][row : row + 32][:, col : col + 32]
256                    v10_window = fd["PRODUCT/SUPPORT_DATA/INPUT_DATA/northward_wind"][
257                        :
258                    ][0][row : row + 32][:, col : col + 32]
259                    sza_window = fd[
260                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/solar_zenith_angle"
261                    ][:][0][row : row + 32][:, col : col + 32]
262                    vza_window = fd[
263                        "PRODUCT/SUPPORT_DATA/GEOLOCATIONS/viewing_zenith_angle"
264                    ][:][0][row : row + 32][:, col : col + 32]
265                    saa_window = fd[
266                        "/PRODUCT/SUPPORT_DATA/GEOLOCATIONS/solar_azimuth_angle"
267                    ][:][0][row : row + 32][:, col : col + 32]
268                    vaa_window = fd[
269                        "/PRODUCT/SUPPORT_DATA/GEOLOCATIONS/viewing_azimuth_angle"
270                    ][:][0][row : row + 32][:, col : col + 32]
271                    f_delta = saa_window - vaa_window
272                    raa_window = np.where(
273                        f_delta < 0, np.abs(f_delta + 180.0), np.abs(f_delta - 180.0)
274                    )
275                    raa_window = abs(180.0 - raa_window)
276                    scattering_angle = -np.cos(np.radians(sza_window)) * np.cos(
277                        np.radians(vza_window)
278                    ) + np.sin(np.radians(sza_window)) * np.sin(
279                        np.radians(vza_window)
280                    ) * np.cos(
281                        np.radians(raa_window)
282                    )
283                    sa_std_window = fd[
284                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/surface_altitude_precision"
285                    ][:][0][row : row + 32][:, col : col + 32]
286                    cloud_fraction_window = fd[
287                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/cloud_fraction_VIIRS_SWIR_IFOV"
288                    ][:][0][row : row + 32][:, col : col + 32]
289                    cirrus_reflectance_window = fd[
290                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/reflectance_cirrus_VIIRS_SWIR"
291                    ][:][0][row : row + 32][:, col : col + 32]
292                    methane_ratio_std_window = fd[
293                        "PRODUCT/SUPPORT_DATA/INPUT_DATA/methane_ratio_weak_strong_standard_deviation"
294                    ][:][0][row : row + 32][:, col : col + 32]
295                    methane_precision_window = fd[
296                        "PRODUCT/methane_mixing_ratio_precision"
297                    ][:][0][row : row + 32][:, col : col + 32]
298                    surface_albedo_window = fd[
299                        "PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/surface_albedo_SWIR"
300                    ][:][0][row : row + 32][:, col : col + 32]
301                    surface_albedo_precision_window = fd[
302                        "PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/surface_albedo_SWIR_precision"
303                    ][:][0][row : row + 32][:, col : col + 32]
304                    aerosol_optical_thickness_window = fd[
305                        "PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/aerosol_optical_thickness_SWIR"
306                    ][:][0][row : row + 32][:, col : col + 32]
307                    times = fd["PRODUCT/time_utc"][:][0][row : row + 32]
308                    try:
309                        parsed_time = [
310                            datetime.datetime.fromisoformat(time_str[:19])
311                            for time_str in times
312                        ]
313                    except ValueError:
314                        continue
315                    if original.mask.sum() < 0.2 * 32 * 32:
316                        yield {
317                            "methane": methane_window,
318                            "lat": lat_window,
319                            "lon": lon_window,
320                            "lat_bounds": lat_bounds_window,
321                            "lon_bounds": lon_bounds_window,
322                            "qa": qa_window,
323                            "time": parsed_time,
324                            "mask": original.mask,
325                            "non_destriped": original,
326                            "u10": u10_window,
327                            "v10": v10_window,
328                            "sza": np.cos(np.radians(sza_window)),
329                            "vza": np.cos(np.radians(vza_window)),
330                            "scattering_angle": scattering_angle,
331                            "sa_std": sa_std_window,
332                            "cloud_fraction": cloud_fraction_window,
333                            "cirrus_reflectance": cirrus_reflectance_window,
334                            "methane_ratio_std": methane_ratio_std_window,
335                            "methane_precision": methane_precision_window,
336                            "surface_albedo": surface_albedo_window,
337                            "surface_albedo_precision": surface_albedo_precision_window,
338                            "aerosol_optical_thickness": aerosol_optical_thickness_window,
339                        }

A generator for 32x32 pixel patches with 50% overlap.

Parameters
  • file_path (str): A path to a TROPOMI netCDF4 file.
Yields
  • dict: A dictionary with patch arrays.