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: (np.float64(61.1592142222353), np.float64(35.74201728362127), np.float64(62.3732728350893), np.float64(36.8302779517758))
         CRS: EPSG:4326
         units: uW/cm^2/SR/nm
        
ei.nc_ds
<xarray.Dataset> Size: 2GB
Dimensions:   (downtrack: 1280, crosstrack: 1242, bands: 285)
Dimensions without coordinates: downtrack, crosstrack, bands
Data variables:
    radiance  (downtrack, crosstrack, bands) float32 2GB ...
Attributes: (12/37)
    ncei_template_version:             NCEI_NetCDF_Swath_Template_v2.0
    summary:                           The Earth Surface Mineral Dust Source ...
    keywords:                          Imaging Spectroscopy, minerals, EMIT, ...
    Conventions:                       CF-1.63
    sensor:                            EMIT (Earth Surface Mineral Dust Sourc...
    instrument:                        EMIT
    ...                                ...
    southernmost_latitude:             35.74201728362127
    spatialResolution:                 0.000542232520256367
    spatial_ref:                       GEOGCS["WGS 84",DATUM["WGS_1984",SPHER...
    geotransform:                      [ 6.11592142e+01  5.42232520e-04 -0.00...
    day_night_flag:                    Day
    title:                             EMIT L1B At-Sensor Calibrated Radiance...

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: (np.float64(61.1592142222353), np.float64(35.74201728362127), np.float64(62.3732728350893), np.float64(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 0x7f05cdf94b90>
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: -9999
        
emit_image_utm_rgb.observation_date_correction_factor
np.float64(3.9465190081273196)
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: -9999
        
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 0x7f05ccfdcfe0>
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 0x7f05cd023c20>
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

georeader tutorials and notebooks are released under a Creative Commons non-commercial 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},
}