Skip to content

EMIT

Install package with EMIT dependecies

EMIT requires the netcdf4 package to read the products. We will also use the pysolar package to convert the EMIT L1B radiances to reflectances.

pip install georeader-spaceml netcdf4 pysolar

Download an EMIT image

from georeader.readers import emit
from georeader.readers import download_utils
import os

dir_emit_files = "emit_database/raw"
os.makedirs(dir_emit_files, exist_ok=True)
# link = 'https://data.lpdaac.earthdatacloud.nasa.gov/lp-prod-protected/EMITL1BRAD.001/EMIT_L1B_RAD_001_20220828T051941_2224004_006/EMIT_L1B_RAD_001_20220828T051941_2224004_006.nc'
link = 'https://data.lpdaac.earthdatacloud.nasa.gov/lp-prod-protected/EMITL1BRAD.001/EMIT_L1B_RAD_001_20220827T060753_2223904_013/EMIT_L1B_RAD_001_20220827T060753_2223904_013.nc'
file_save = os.path.join(dir_emit_files, os.path.basename(link))

emit.download_product(link, file_save)
File emit_database/raw/EMIT_L1B_RAD_001_20220827T060753_2223904_013.nc exists. It won't be downloaded again

'emit_database/raw/EMIT_L1B_RAD_001_20220827T060753_2223904_013.nc'

Create and inspec emit object

EMIT objects let you open an EMIT nc file without loading the content of the file in memory. The object in the cell below has 285 spectral bands. For the API description of the EMIT class see the emit module. Since the object follows the API of georeader, you can read from the rasters using the functions from the read module.

# file_save = "emit_database/EMIT_L1B_RAD_001_20220827T060753_2223904_013.nc"
ei = emit.EMITImage(file_save)
ei
 
         File: emit_database/raw/EMIT_L1B_RAD_001_20220827T060753_2223904_013.nc
         Transform: | 0.00,-0.00, 61.16|
|-0.00,-0.00, 36.83|
| 0.00, 0.00, 1.00|
         Shape: (285, 2007, 2239)
         Resolution: (0.0005422325202530942, 0.0005422325202530942)
         Bounds: (61.1592142222353, 35.74201728362127, 62.3732728350893, 36.8302779517758)
         CRS: EPSG:4326
         units: uW/cm^2/SR/nm
        
ei.nc_ds
<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF4 data model, file format HDF5):
    ncei_template_version: NCEI_NetCDF_Swath_Template_v2.0
    summary: The Earth Surface Mineral Dust Source Investigation (EMIT) is an Earth Ventures-Instrument (EVI-4) Mission that maps the surface mineralogy of arid dust source regions via imaging spectroscopy in the visible and short-wave infrared (VSWIR). Installed on the International Space Station (ISS), the EMIT instrument is a Dyson imaging spectrometer that uses contiguous spectroscopic measurements from 410 to 2450 nm to resolve absoprtion features of iron oxides, clays, sulfates, carbonates, and other dust-forming minerals. During its one-year mission, EMIT will observe the sunlit Earth's dust source regions that occur within +/-52° latitude and produce maps of the source regions that can be used to improve forecasts of the role of mineral dust in the radiative forcing (warming or cooling) of the atmosphere.\n\nThis file contains L1B at-sensor calibrated radiances. The radiance calibration occurs in two basic stages: 1) transforming raw digital numbers into radiance units using a calibrated radiometric response and correcting for electronic artifacts, and 2) a correction for instrument optical effects which results in an absolute spectral wavelength calibration. The radiance file contains radiance for each of 285 channels in units of microwatts per centimeter per centimeter squared per steradian. Geolocation data (latitude, longitude, height) and a lookup table to project the data are also included.
    keywords: Imaging Spectroscopy, minerals, EMIT, dust, radiative forcing
    Conventions: CF-1.63
    sensor: EMIT (Earth Surface Mineral Dust Source Investigation)
    instrument: EMIT
    platform: ISS
    institution: NASA Jet Propulsion Laboratory/California Institute of Technology
    license: https://science.nasa.gov/earth-science/earth-science-data/data-information-policy/
    naming_authority: LPDAAC
    date_created: 2023-01-21T12:24:18Z
    keywords_vocabulary: NASA Global Change Master Directory (GCMD) Science Keywords
    stdname_vocabulary: NetCDF Climate and Forecast (CF) Metadata Convention
    creator_name: Jet Propulsion Laboratory/California Institute of Technology
    creator_url: https://earth.jpl.nasa.gov/emit/
    project: Earth Surface Mineral Dust Source Investigation
    project_url: https://emit.jpl.nasa.gov/
    publisher_name: NASA LPDAAC
    publisher_url: https://lpdaac.usgs.gov
    publisher_email: lpdaac@usgs.gov
    identifier_product_doi_authority: https://doi.org
    flight_line: emit20220827t060753_o23904_s001
    time_coverage_start: 2022-08-27T06:07:53+0000
    time_coverage_end: 2022-08-27T06:08:05+0000
    software_build_version: 010603
    product_version: V001
    history: PGE Run Command: {python /beegfs/store/emit/ops/repos/emit-sds-l1b/emitrdn.py --mode default --level INFO --log_file /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_rdn_b0106_v01_pge.log /beegfs/store/emit/ops/data/acquisitions/20220827/emit20220827t060753/l1a/emit20220827t060753_o23904_s001_l1a_raw_b0106_v01.img /beegfs/store/emit/ops/data/acquisitions/20220827/emit20220827t054803/l1a/emit20220827t054803_o23904_s000_l1a_raw_b0106_v01.img /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/l1b_config.json /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_rdn_b0106_v01.img /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_bandmask_b0106_v01.img;python /beegfs/store/emit/ops/repos/emit-sds-l1b/utils/fitflatfield.py /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_rdn_b0106_v01.img /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_rdn_destripe_b0106_v01.img /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_destripeff_b0106_v01.img /tmp/emit/ops/emit20220827t060753_emit.L1BCalibrate_20221112t080244/output/emit20220827t060753_o23904_s001_l1b_destripedark_b0106_v01.img}, PGE Input Files: {srf_correction_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_SpectralScatter_20220406, crf_correction_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_SpatialScatter_20220406, bad_element_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_BadElements_20220307, spectral_calibration_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_Wavelengths_20220817.txt, ghost_map_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_GhostMap_20220424.json, linearity_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_LinearityBasis_20220504, linearity_map_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_LinearityMap_20220504, flat_field_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_FlatField_20220825, radiometric_coefficient_file=/beegfs/store/emit/ops/repos/emit-sds-l1b/data/emit/EMIT_RadiometricCoeffs_20220901.txt, dark_file=/beegfs/store/emit/ops/data/acquisitions/20220827/emit20220827t054803/l1a/emit20220827t054803_o23904_s000_l1a_raw_b0106_v01.img}
    crosstrack_orientation: as seen on ground
    easternmost_longitude: 61.1592142222353
    northernmost_latitude: 36.8302779517758
    westernmost_longitude: 62.3732728350893
    southernmost_latitude: 35.74201728362127
    spatialResolution: 0.000542232520256367
    spatial_ref: GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]
    geotransform: [ 6.11592142e+01  5.42232520e-04 -0.00000000e+00  3.68302780e+01
 -0.00000000e+00 -5.42232520e-04]
    day_night_flag: Day
    title: EMIT L1B At-Sensor Calibrated Radiance Data 60 m V001
    dimensions(sizes): downtrack(1280), crosstrack(1242), bands(285), ortho_y(2007), ortho_x(2239)
    variables(dimensions): float32 radiance(downtrack, crosstrack, bands)
    groups: sensor_band_parameters, location
ei.wavelengths
array([ 381.00558,  388.4092 ,  395.81583,  403.2254 ,  410.638  ,
        418.0536 ,  425.47214,  432.8927 ,  440.31726,  447.7428 ,
        455.17035,  462.59888,  470.0304 ,  477.46292,  484.89743,
        492.33292,  499.77142,  507.2099 ,  514.6504 ,  522.0909 ,
        529.5333 ,  536.9768 ,  544.42126,  551.8667 ,  559.3142 ,
        566.7616 ,  574.20905,  581.6585 ,  589.108  ,  596.55835,
        604.0098 ,  611.4622 ,  618.9146 ,  626.36804,  633.8215 ,
        641.2759 ,  648.7303 ,  656.1857 ,  663.6411 ,  671.09753,
        678.5539 ,  686.0103 ,  693.4677 ,  700.9251 ,  708.38354,
        715.84094,  723.2993 ,  730.7587 ,  738.2171 ,  745.6765 ,
        753.1359 ,  760.5963 ,  768.0557 ,  775.5161 ,  782.97754,
        790.4379 ,  797.89935,  805.36176,  812.8232 ,  820.2846 ,
        827.746  ,  835.2074 ,  842.66986,  850.1313 ,  857.5937 ,
        865.0551 ,  872.5176 ,  879.98004,  887.44147,  894.90393,
        902.3664 ,  909.82886,  917.2913 ,  924.7538 ,  932.21625,
        939.6788 ,  947.14026,  954.6027 ,  962.0643 ,  969.5268 ,
        976.9883 ,  984.4498 ,  991.9114 ,  999.37286, 1006.8344 ,
       1014.295  , 1021.7566 , 1029.2172 , 1036.6777 , 1044.1383 ,
       1051.5989 , 1059.0596 , 1066.5201 , 1073.9797 , 1081.4404 ,
       1088.9    , 1096.3597 , 1103.8184 , 1111.2781 , 1118.7368 ,
       1126.1964 , 1133.6552 , 1141.1129 , 1148.5717 , 1156.0304 ,
       1163.4882 , 1170.9459 , 1178.4037 , 1185.8616 , 1193.3184 ,
       1200.7761 , 1208.233  , 1215.6898 , 1223.1467 , 1230.6036 ,
       1238.0596 , 1245.5154 , 1252.9724 , 1260.4283 , 1267.8833 ,
       1275.3392 , 1282.7942 , 1290.2502 , 1297.7052 , 1305.1603 ,
       1312.6144 , 1320.0685 , 1327.5225 , 1334.9756 , 1342.4287 ,
       1349.8818 , 1357.3351 , 1364.7872 , 1372.2384 , 1379.6907 ,
       1387.1418 , 1394.5931 , 1402.0433 , 1409.4937 , 1416.944  ,
       1424.3933 , 1431.8427 , 1439.292  , 1446.7404 , 1454.1888 ,
       1461.6372 , 1469.0847 , 1476.5321 , 1483.9796 , 1491.4261 ,
       1498.8727 , 1506.3192 , 1513.7649 , 1521.2104 , 1528.655  ,
       1536.1007 , 1543.5454 , 1550.9891 , 1558.4329 , 1565.8766 ,
       1573.3193 , 1580.7621 , 1588.205  , 1595.6467 , 1603.0886 ,
       1610.5295 , 1617.9705 , 1625.4104 , 1632.8513 , 1640.2903 ,
       1647.7303 , 1655.1694 , 1662.6074 , 1670.0455 , 1677.4836 ,
       1684.9209 , 1692.358  , 1699.7952 , 1707.2314 , 1714.6667 ,
       1722.103  , 1729.5383 , 1736.9727 , 1744.4071 , 1751.8414 ,
       1759.2749 , 1766.7084 , 1774.1418 , 1781.5743 , 1789.007  ,
       1796.4385 , 1803.8701 , 1811.3008 , 1818.7314 , 1826.1611 ,
       1833.591  , 1841.0206 , 1848.4495 , 1855.8773 , 1863.3052 ,
       1870.733  , 1878.16   , 1885.5869 , 1893.013  , 1900.439  ,
       1907.864  , 1915.2892 , 1922.7133 , 1930.1375 , 1937.5607 ,
       1944.9839 , 1952.4071 , 1959.8295 , 1967.2518 , 1974.6732 ,
       1982.0946 , 1989.515  , 1996.9355 , 2004.355  , 2011.7745 ,
       2019.1931 , 2026.6118 , 2034.0304 , 2041.4471 , 2048.865  ,
       2056.2808 , 2063.6965 , 2071.1123 , 2078.5273 , 2085.9421 ,
       2093.3562 , 2100.769  , 2108.1821 , 2115.5942 , 2123.0063 ,
       2130.4175 , 2137.8289 , 2145.239  , 2152.6482 , 2160.0576 ,
       2167.467  , 2174.8755 , 2182.283  , 2189.6904 , 2197.097  ,
       2204.5034 , 2211.9092 , 2219.3147 , 2226.7195 , 2234.1233 ,
       2241.5269 , 2248.9297 , 2256.3328 , 2263.7346 , 2271.1365 ,
       2278.5376 , 2285.9387 , 2293.3386 , 2300.7378 , 2308.136  ,
       2315.5342 , 2322.9326 , 2330.3298 , 2337.7263 , 2345.1216 ,
       2352.517  , 2359.9126 , 2367.3071 , 2374.7007 , 2382.0935 ,
       2389.486  , 2396.878  , 2404.2695 , 2411.6604 , 2419.0513 ,
       2426.4402 , 2433.8303 , 2441.2183 , 2448.6064 , 2455.9944 ,
       2463.3816 , 2470.7678 , 2478.153  , 2485.5386 , 2492.9238 ],
      dtype=float32)
ei.time_coverage_start, ei.time_coverage_end
(datetime.datetime(2022, 8, 27, 6, 7, 53, tzinfo=datetime.timezone.utc),
 datetime.datetime(2022, 8, 27, 6, 8, 5, tzinfo=datetime.timezone.utc))
import geopandas as gpd

gpd.GeoDataFrame(geometry=[ei.footprint()],
                 crs=ei.crs).explore()
Make this Notebook Trusted to load map: File -> Trust Notebook

Load RGB

Select the RGB bands, we see that the raster has only 3 channels now (see Shape in the output of the cell below). The ei object has an attribute called wavelengths with the central wavelength of the hyperspectral band.

import numpy as np
wavelengths_read = np.array([640, 550, 460])

bands_read = np.argmin(np.abs(wavelengths_read[:, np.newaxis] - ei.wavelengths), axis=1).tolist()
ei_rgb = ei.read_from_bands(bands_read)
ei_rgb
 
         File: emit_database/raw/EMIT_L1B_RAD_001_20220827T060753_2223904_013.nc
         Transform: | 0.00,-0.00, 61.16|
|-0.00,-0.00, 36.83|
| 0.00, 0.00, 1.00|
         Shape: (3, 2007, 2239)
         Resolution: (0.0005422325202530942, 0.0005422325202530942)
         Bounds: (61.1592142222353, 35.74201728362127, 62.3732728350893, 36.8302779517758)
         CRS: EPSG:4326
         units: uW/cm^2/SR/nm
        

Normalize radiance to reflectance

import pandas as pd
import matplotlib.pyplot as plt
from georeader import reflectance

thuiller = reflectance.load_thuillier_irradiance() # (dataframe with 8191 rows, 2 colums -&gt; Nanometer, Radiance(mW/m2/nm)

ei_rgb.wavelengths, ei_rgb.fwhm # (K,) vectors with the center wavelength and the FWHM

response = reflectance.srf(ei_rgb.wavelengths, ei_rgb.fwhm, thuiller["Nanometer"].values) # (8191, K)

colors = ["red","green", "blue"]
fig, ax = plt.subplots(1,1,figsize=(12,4))

ax.plot(thuiller["Nanometer"].values,
        thuiller["Radiance(mW/m2/nm)"].values,
        label="Thuiller irradiance")
ax.set_ylabel("Solar irradiance (mW/m$^2$/nm)")
ax = ax.twinx()
for k in range(3):
    ax.plot(thuiller["Nanometer"].values, response[:, k], 
            label=f"{wavelengths_read[k]}nm",c=colors[k])
ax.set_xlabel("Wavelength nm")
ax.set_ylabel("SRF")
ax.set_xlim(410,700)
ax.legend(loc="upper left")
<matplotlib.legend.Legend at 0x7f03eb294220>
No description has been provided for this image
# solar_irradiance_norm = thuiller["Radiance(mW/m2/nm)"].values.dot(response) # mW/m$^2$/nm
# solar_irradiance_norm/=1_000  # W/m$^2$/nm
# solar_irradiance_norm
# ei_rgb_local = ei_rgb.load(as_reflectance=False)
# ei_rgb_local = reflectance.radiance_to_reflectance(ei_rgb_local, solar_irradiance_norm,
#                                                    ei.time_coverage_start,units=units)
ei_rgb_local = ei_rgb.load(as_reflectance=True)
from georeader.plot import show

show((ei_rgb_local).clip(0,1), 
     mask=ei_rgb_local.values == ei_rgb_local.fill_value_default)
<Axes: >
No description has been provided for this image

Reproject to UTM

import georeader

crs_utm = georeader.get_utm_epsg(ei.footprint("EPSG:4326"))
emit_image_utm = ei.to_crs(crs_utm)
emit_image_utm_rgb = emit_image_utm.read_from_bands(bands_read)
emit_image_utm_rgb_local = emit_image_utm_rgb.load(as_reflectance=True)
emit_image_utm_rgb_local
 
         Transform: | 60.00, 0.00, 333546.51|
| 0.00,-60.00, 4077625.88|
| 0.00, 0.00, 1.00|
         Shape: (3, 2036, 1844)
         Resolution: (60.0, 60.0)
         Bounds: (333546.5136802632, 3955465.8769401284, 444186.5136802632, 4077625.8769401284)
         CRS: EPSG:32641
         fill_value_default: 0
        
emit_image_utm_rgb.observation_date_correction_factor
3.9465190869822364
show((emit_image_utm_rgb_local).clip(0,1), 
     mask=emit_image_utm_rgb_local.values == emit_image_utm_rgb_local.fill_value_default,
     add_scalebar=True)
<Axes: >
No description has been provided for this image
show(emit_image_utm.elevation(), add_colorbar_next_to=True, add_scalebar=True,
     mask=True,title="Elevation")
<Axes: title={'center': 'Elevation'}>
No description has been provided for this image

Subset EMIT image

from georeader import read

point_tup = (61.28, 36.21)
ei_subset = read.read_from_center_coords(emit_image_utm_rgb, point_tup, 
                                         shape=(200,200),crs_center_coords="EPSG:4326")

ei_subset_local = ei_subset.load(as_reflectance=True)
ei_subset_local
 
         Transform: | 60.00, 0.00, 339366.51|
| 0.00,-60.00, 4014625.88|
| 0.00, 0.00, 1.00|
         Shape: (3, 200, 200)
         Resolution: (60.0, 60.0)
         Bounds: (339366.5136802632, 4002625.8769401284, 351366.5136802632, 4014625.8769401284)
         CRS: EPSG:32641
         fill_value_default: 0
        
from georeader.plot import add_shape_to_plot
from shapely.geometry import Point

ax = show((ei_subset_local).clip(0,1),
          mask=ei_subset_local.values == ei_rgb_local.fill_value_default,
         add_scalebar=True)
add_shape_to_plot(Point(*point_tup),crs_shape="EPSG:4326",
                  crs_plot=ei_subset_local.crs, ax=ax)
<Axes: >
No description has been provided for this image

Load image non-orthorectified

Full image

import matplotlib.pyplot as plt

ei_rgb_raw = ei_rgb.load_raw(transpose=False)
plt.imshow((ei_rgb_raw/12).clip(0,1))
<matplotlib.image.AxesImage at 0x7f03db62f370>
No description has been provided for this image

Subset

ei_rgb_subset = ei_subset.load_raw(transpose=False)
plt.imshow((ei_rgb_subset/12).clip(0,1))
<matplotlib.image.AxesImage at 0x7f03db6b0df0>
No description has been provided for this image

Load L2AMask

The L2AMask is stored in a separated file. The mask array has the following variables:

emit_image_utm.mask_bands
array(['Cloud flag', 'Cirrus flag', 'Water flag', 'Spacecraft Flag',
       'Dilated Cloud Flag', 'AOD550', 'H2O (g cm-2)', 'Aggregate Flag'],
      dtype=object)
# mask filtering cloudy pixels
show(emit_image_utm.validmask(), add_scalebar=True,vmin=0, vmax=1,
     mask=True,title="Valid Mask")
<Axes: title={'center': 'Valid Mask'}>
No description has been provided for this image

Load metadata

Metadata is stored in a separated file (with suffix _OBS_ instead of _RAD_). In metadata we have the following variables:

emit_image_utm.observation_bands
array(['Path length (sensor-to-ground in meters)',
       'To-sensor azimuth (0 to 360 degrees CW from N)',
       'To-sensor zenith (0 to 90 degrees from zenith)',
       'To-sun azimuth (0 to 360 degrees CW from N)',
       'To-sun zenith (0 to 90 degrees from zenith)',
       'Solar phase (degrees between to-sensor and to-sun vectors in principal plane)',
       'Slope (local surface slope as derived from DEM in degrees)',
       'Aspect (local surface aspect 0 to 360 degrees clockwise from N)',
       'Cosine(i) (apparent local illumination factor based on DEM slope and aspect and to sun vector)',
       'UTC Time (decimal hours for mid-line pixels)',
       'Earth-sun distance (AU)'], dtype=object)
show(emit_image_utm.sza(), add_colorbar_next_to=True, add_scalebar=True,
     mask=True,title="Solar Zenith Angle")
<Axes: title={'center': 'Solar Zenith Angle'}>
No description has been provided for this image
show(emit_image_utm.vza(), add_colorbar_next_to=True, add_scalebar=True,
     mask=True,title="Viewing Zenith Angle")
<Axes: title={'center': 'Viewing Zenith Angle'}>
No description has been provided for this image
show(emit_image_utm.observation('Path length (sensor-to-ground in meters)'), 
     add_colorbar_next_to=True, add_scalebar=True,
     mask=True,title='Path length (sensor-to-ground in meters)')
<Axes: title={'center': 'Path length (sensor-to-ground in meters)'}>
No description has been provided for this image
show(emit_image_utm.observation('Slope (local surface slope as derived from DEM in degrees)'), 
     add_colorbar_next_to=True, add_scalebar=True,
     mask=True,title='Slope (local surface slope as derived from DEM in degrees)')
<Axes: title={'center': 'Slope (local surface slope as derived from DEM in degrees)'}>
No description has been provided for this image

Licence

The georeader package is published under a GNU Lesser GPL v3 licence

If you find this work useful please cite:

@article{ruzicka_starcop_2023,
    title = {Semantic segmentation of methane plumes with hyperspectral machine learning models},
    volume = {13},
    issn = {2045-2322},
    url = {https://www.nature.com/articles/s41598-023-44918-6},
    doi = {10.1038/s41598-023-44918-6},
    number = {1},
    journal = {Scientific Reports},
    author = {Růžička, Vít and Mateo-Garcia, Gonzalo and Gómez-Chova, Luis and Vaughan, Anna, and Guanter, Luis and Markham, Andrew},
    month = nov,
    year = {2023},
    pages = {19999},
}