NEON In-Situ Comparison¶
Let's put it all together by running ISOFIT for a sensor-specific example and compare ISOFIT derived reflectance to field-collected spectra.
Prerequisites:
- Have ISOFIT installed and sRTMnet configured.
- Download the subset_data.zip and place the unzipped
datadirectory into tutorial's repository root. Alternatively, useisofit download examples --neonto automate this process.
Note: If you downloaded the ISOFIT extra data via isofit download, both sRTMnet and the NEON data files will be installed correctly and available with default settings for this notebook.
# Jupyter magics
%matplotlib inline
# Builtin
import os
import shutil
from pathlib import Path
from types import SimpleNamespace
# External
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
from spectral.io import envi
# Internal
import isofit
from isofit.data import env
from isofit.utils.apply_oe import apply_oe
from isofit.utils.surface_model import surface_model
# Below are the default values for the ISOFIT environment. Change these if your environment differs
env.load('~/.isofit/isofit.ini') # Ini file to load
env.changeSection('DEFAULT') # Section of the ini to use
# env.changeBase('~./isofit') # Base path for ISOFIT extras (data, examples, etc)
# env.changePath('srtmnet', '/path/to/sRTMnet_v120.h5') # Overwrite the path to sRTMnet - copy this line for other products such as sixs if in non-default locations
print('Using environment paths:')
for key, path in env.items():
print(f"- {key} = {path}")
Using environment paths: - data = /home/runner/work/isofit-tutorials/data - examples = /home/runner/work/isofit-tutorials/isofit-tutorials - srtmnet = /home/runner/work/isofit-tutorials/srtmnet - sixs = /home/runner/work/isofit-tutorials/sixs - surface = /home/runner/work/isofit-tutorials/surface - plots = /home/runner/work/isofit-tutorials/plots - libradtran = /home/runner/work/isofit-tutorials/libradtran - windows = /home/runner/work/isofit-tutorials/windows - srtmnet.file = sRTMnet_v120.h5 - srtmnet.aux = sRTMnet_v120_aux.npz - libradtran.version = libRadtran-2.0.6
Setup¶
ISOFIT needs at minimum three pieces as input:
1. Radiance measurements (rdn)
2. Observation values (obs)
3. Location information (loc)
This sample dataset from NEON has radiance and observation data, but no location values (more recent NEON datasets include the location file). However, we can 'fake' the location file with sufficient accuracy for ISOFIT to run successfully. Note that there are data available for two dates:
Radiance
├── 173647
│ ├── NIS01_20210403_173647_obs_ort
│ ├── NIS01_20210403_173647_obs_ort.hdr
│ ├── NIS01_20210403_173647_rdn_ort
│ └── NIS01_20210403_173647_rdn_ort.hdr
└── 174150
├── NIS01_20210403_174150_obs_ort
├── NIS01_20210403_174150_obs_ort.hdr
├── NIS01_20210403_174150_rdn_ort
└── NIS01_20210403_174150_rdn_ort.hdr
These files have corresponding in situ data as well, and below we've encoded the locations of each, which we can use to help subset data files.
# Extract the image locations of each point of interest (POI)
# These are defined in the NEON report as pixel locations, so we round here to convert to indices
report = {}
report['173647'] = { # Upp L Y | Low R Y | Upp L X | Low R X
'WhiteTarp': np.round([2224.9626, 2230.9771, 316.0078, 324.9385,]).astype(int),
'BlackTarp': np.round([2224.9626, 2231.0032, 328.0086, 333.9731,]).astype(int),
'Veg' : np.round([2245.0381, 2258.8103, 343.9006, 346.9423,]).astype(int),
'RoadEW' : np.round([2214.9905, 2216.9978, 348.9902, 373.0080,]).astype(int),
'RoadNS' : np.round([2205.9580, 2225.9612, 357.9536, 359.9608,]).astype(int)
}
report['174150'] = { # Upp L Y | Low R Y | Upp L X | Low R X
'WhiteTarp': np.round([653.9626, 659.9771, 3143.0078, 3151.9385]).astype(int),
'BlackTarp': np.round([653.9626, 660.0032, 3155.0086, 3160.9731]).astype(int),
'Veg' : np.round([674.0381, 687.8103, 3170.9006, 3173.9423]).astype(int),
'RoadEW' : np.round([643.9905, 645.9978, 3175.9902, 3200.0080]).astype(int),
'RoadNS' : np.round([634.9580, 654.9612, 3184.9536, 3186.9608]).astype(int)
}
# Which NEON date to process - change this to process a different date
neon_id = list(report.keys())[0]
neon_str = f"NIS01_20210403_{neon_id}"
# Select the locations from the neon id -- roi == Regions of Interest
roi = report[neon_id]
# Set the paths for this tutorial
base = Path(env.path('examples', 'NEON'))
raws = base / 'data'
data = base / 'neon_subset'
paths = SimpleNamespace(
rdn = str(data / f'{neon_str}_rdn_ort'),
loc = str(data / f'{neon_str}_loc_ort'),
obs = str(data / f'{neon_str}_obs_ort'),
insitu = raws,
output = base / 'output',
working = base / f'output/NIS01_20210403_{neon_id}',
surface = str(base / 'output/surface.mat'),
surface_config = env.path('examples', '20171108_Pasadena', 'configs', 'ang20171108t184227_surface.json')
)
paths.output.mkdir(exist_ok=True, parents=True)
# If you are missing either an OBS file or a LOC file, use these to create faked versions based off the radiance file
# This should not be needed if using the provided data
# Using this may cause the below plots to not generate the same results
# from isotuts import neon_utils
# paths.obs = neon_utils.fakeOBS(
# f"{paths.rdn}.hdr",
# sea = 153.4481201171875,
# sez = 178.3806858062744,
# soa = 39.8218994140625,
# soz = 39.8218994140625,
# slope = 31.813383102416992
# )[:-4] # Remove the .hdr extension from the return
# paths.loc = neon_utils.fakeLOC(
# rdn = f"{paths.rdn}.hdr",
# lon = -105.237000,
# lat = 40.125000,
# elv = 1689.0
# )[:-4]
Apply OE¶
The next part walks through running the ISOFIT utility script isofit/utils/apply_oe.py. This is the first step of executing ISOFIT and will generate a default configuration.
# First build a surface model
surface_model(
config_path = paths.surface_config,
output_path = paths.surface,
wavelength_path = f"{paths.rdn}.hdr"
)
0 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb']
1 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb']
2 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb'] 3 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb']
4 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb'] 5 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb'] 6 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb']
7 ['/home/runner/work/isofit-tutorials/data/reflectance/surface_model_ucsb']
# Add a ray shutdown, just in case this is being re-called
import ray
ray.shutdown()
# Cleanup any previous runs; comment this out if you want to preserve a previous run's output
if Path(paths.working).exists():
shutil.rmtree(paths.working)
apply_oe(
input_radiance = paths.rdn, # Radiance
input_loc = paths.loc, # Location
input_obs = paths.obs, # Observations
working_directory = str(paths.working), # Output directory
sensor = "neon",
surface_path = paths.surface, # Surface priors - often changes
emulator_base = f"{env.srtmnet}/sRTMnet_v120.h5",
surface_category = "multicomponent_surface",
modtran_path = None,
atmosphere_type = "ATM_MIDLAT_SUMMER", # MODTRAN
aerosol_climatology_path = None, # MODTRAN
rdn_factors_path = None, # RCC update used 'on the fly'
model_discrepancy_path = None, # Model discrepancy term - handle things like unknown radiative transfer model effects
channelized_uncertainty_path = None, # Channelized uncertainty - if you have an instrument model
multiple_restarts = False, # Useful if the AOD conditions are really challenging
presolve = True, # Attempts to solve for the right wv range
empirical_line = False, # wavelength-specific local linear interpolation between radiance and reflectance
analytical_line = True, # mathematical representation of OE given that the atmsophere is known
segmentation_size = 10,
num_neighbors = [5],
atm_sigma = [0.5, 0.5],
pressure_elevation = False,
n_cores = os.cpu_count(),
)
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Checking input data files...
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | ...Data file checks complete
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Setting up files and directories....
INFO:2026-05-20,21:54:39 || template_construction.py:__init__() | Flightline ID: NIS01_20210403_173647
INFO:2026-05-20,21:54:39 || template_construction.py:__init__() | no noise path found, proceeding without
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | ...file/directory setup complete
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Using inversion windows: [[350.0, 1360.0], [1410, 1800.0], [1970.0, 2500.0]]
INFO:2026-05-20,21:54:39 || template_construction.py:get_wavelengths() | No wavelength file provided. Obtaining wavelength grid from ENVI header of radiance cube.
INFO:2026-05-20,21:54:39 || template_construction.py:get_wavelengths() | Wavelength units of nm inferred...converting to microns
WARNING:2026-05-20,21:54:39 || template_construction.py:check_surface_model() | Center wavelengths provided in surface model file do not match wavelengths in radiance cube. Please consider rebuilding your surface model for optimal performance.
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Observation means:
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Path (km): 1.0036078691482544
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | To-sensor azimuth (deg): 153.4481201171875
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | To-sensor zenith (deg): 1.619314193725586
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | To-sun azimuth (deg): 145.23248291015625
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | To-sun zenith (deg): 39.8218994140625
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Relative to-sun azimuth (deg): 31.813383102416992
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Altitude (km): 2.692207098007202
INFO:2026-05-20,21:54:39 || apply_oe.py:apply_oe() | Segmenting...
2026-05-20 21:54:44,657 INFO worker.py:2012 -- Started a local Ray instance.
/home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/ray/_private/worker.py:2051: FutureWarning: Tip: In future versions of Ray, Ray will no longer override accelerator visible devices env var if num_gpus=0 or num_gpus=None (default). To enable this behavior and turn off this error message, set RAY_ACCEL_ENV_VAR_OVERRIDE_ON_ZERO=0 warnings.warn(
(segment_chunk pid=7097) INFO:2026-05-20,21:54:47 ||| 0: starting (segment_chunk pid=7097) INFO:2026-05-20,21:54:48 ||| 0: completing INFO:2026-05-20,21:54:48 || apply_oe.py:apply_oe() | Extracting /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/input/NIS01_20210403_173647_subs_rdn
2026-05-20 21:54:48,044 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
(extract_chunk pid=7097) INFO:2026-05-20,21:54:48 ||| ini does not exist, falling back to defaults: ~/.isofit/isofit.ini
(extract_chunk pid=7097) INFO:2026-05-20,21:54:50 ||| 0: starting
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Extracting /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/input/NIS01_20210403_173647_subs_obs
2026-05-20 21:54:50,073 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Extracting /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/input/NIS01_20210403_173647_subs_loc
2026-05-20 21:54:50,101 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Skipping None, because is not a path.
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Skipping None, because is not a path.
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Pre-solve H2O grid: [0.2 0.49 0.79 1.08 1.37 1.67 1.96 2.25 2.55 2.84]
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Writing H2O pre-solve configuration file.
INFO:2026-05-20,21:54:50 || ini.py:toTemplate() | Saved converted json to: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/config/NIS01_20210403_173647_h2o.json.tmpl
INFO:2026-05-20,21:54:50 || apply_oe.py:apply_oe() | Run ISOFIT initial guess
WARNING:2026-05-20,21:54:50 || __init__.py:checkNumThreads() | ****************************************************************************************** ! Number of threads is greater than 1 (currently: 4), this may greatly impact performance ! Please set this the environment variables 'MKL_NUM_THREADS' and 'OMP_NUM_THREADS' to '1' ******************************************************************************************
INFO:2026-05-20,21:54:50 || configs.py:create_new_config() | Loading config file: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/config/NIS01_20210403_173647_h2o.json
INFO:2026-05-20,21:54:50 || configs.py:get_config_errors() | Checking config sections for configuration issues
INFO:2026-05-20,21:54:50 || configs.py:get_config_errors() | Configuration file checks complete, no errors found.
/home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice return _methods._mean(a, axis=axis, dtype=dtype, /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide ret = ret.dtype.type(ret / rcount) (extract_chunk pid=7097) INFO:2026-05-20,21:54:50 ||| 0: starting (extract_chunk pid=7097) INFO:2026-05-20,21:54:50 ||| 0: starting
2026-05-20 21:54:50,409 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
INFO:2026-05-20,21:54:50 || isofit.py:run() | Running surfaces: uniform_surface
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:__init__() | Loading from wavelength_file: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/data/wavelengths.txt
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:__init__() | No LUT store found, beginning initialization and simulations
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:__init__() | Initializing LUT file
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:runSimulations() | Running any pre-sim functions
INFO:2026-05-20,21:54:50 || sRTMnet.py:preSim() | Creating a simulator configuration
INFO:2026-05-20,21:54:50 || sRTMnet.py:preSim() | Building simulator and executing (6S)
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:__init__() | No LUT store found, beginning initialization and simulations
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:__init__() | Initializing LUT file
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:runSimulations() | Running any pre-sim functions
INFO:2026-05-20,21:54:50 || radiative_transfer_engine.py:runSimulations() | Executing parallel simulations
INFO:2026-05-20,21:54:59 || common.py:__call__() | 20.00% simulations complete (elapsed: 0:00:08.156780, rate: 0:00:00.815678, eta: 0:01:13.411020)
INFO:2026-05-20,21:54:59 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:54:59 || common.py:__call__() | 30.00% simulations complete (elapsed: 0:00:08.490390, rate: 0:00:00.849039, eta: 0:00:33.961560)
INFO:2026-05-20,21:54:59 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:54:59 || common.py:__call__() | 40.00% simulations complete (elapsed: 0:00:08.746415, rate: 0:00:00.874642, eta: 0:00:20.408302)
INFO:2026-05-20,21:54:59 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:54:59 || common.py:__call__() | 50.00% simulations complete (elapsed: 0:00:08.996768, rate: 0:00:00.899677, eta: 0:00:13.495152)
INFO:2026-05-20,21:54:59 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:55:03 || common.py:__call__() | 60.00% simulations complete (elapsed: 0:00:12.741463, rate: 0:00:01.274146, eta: 0:00:12.741463)
INFO:2026-05-20,21:55:03 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:55:03 || common.py:__call__() | 70.00% simulations complete (elapsed: 0:00:13.000067, rate: 0:00:01.300007, eta: 0:00:08.666711)
INFO:2026-05-20,21:55:03 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:55:04 || common.py:__call__() | 80.00% simulations complete (elapsed: 0:00:13.235667, rate: 0:00:01.323567, eta: 0:00:05.672429)
INFO:2026-05-20,21:55:04 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:55:04 || common.py:__call__() | 90.00% simulations complete (elapsed: 0:00:13.438088, rate: 0:00:01.343809, eta: 0:00:03.359522)
INFO:2026-05-20,21:55:04 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:55:06 || common.py:__call__() | 100.00% simulations complete (elapsed: 0:00:15.391199, rate: 0:00:01.539120, eta: 0:00:01.710133)
INFO:2026-05-20,21:55:06 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:55:06 || radiative_transfer_engine.py:runSimulations() | Running any post-sim functions
INFO:2026-05-20,21:55:06 || radiative_transfer_engine.py:runSimulations() | Saving post-sim data to index zero of all dimensions except wl
INFO:2026-05-20,21:55:07 || luts.py:load() | Loading LUT into memory
WARNING:2026-05-20,21:55:07 || luts.py:load() | thermal_upwelling is fully NaN, leaving as-is
WARNING:2026-05-20,21:55:07 || luts.py:load() | thermal_downwelling is fully NaN, leaving as-is
INFO:2026-05-20,21:55:07 || sRTMnet.py:preSim() | Interpolating simulator quantities to emulator size
INFO:2026-05-20,21:55:07 || sRTMnet.py:preSim() | Loading and predicting with emulator on 4 cores
INFO:2026-05-20,21:55:08 || radiative_transfer_engine.py:runSimulations() | Saving pre-sim data to index zero of all dimensions except wl
INFO:2026-05-20,21:55:08 || radiative_transfer_engine.py:runSimulations() | Running any post-sim functions
INFO:2026-05-20,21:55:08 || luts.py:load() | Loading LUT into memory
WARNING:2026-05-20,21:55:08 || luts.py:load() | thermal_upwelling is fully NaN, leaving as-is
WARNING:2026-05-20,21:55:08 || luts.py:load() | thermal_downwelling is fully NaN, leaving as-is
INFO:2026-05-20,21:55:09 || isofit.py:run() | Beginning 420 inversions in 40 chunks using 4 cores
(Worker pid=7400) ERROR:2026-05-20,21:55:16 ||| [x] Data path does not exist (Worker pid=7400) ERROR:2026-05-20,21:55:16 ||| The following path does not exist, please verify your installation environment: /home/runner/.isofit/data/earth_sun_distance.txt (Worker pid=7400) WARNING:2026-05-20,21:55:16 ||| Earth-sun-distance file not found on system. Proceeding without might cause some inaccuracies down the line. (Worker pid=7400) INFO:2026-05-20,21:55:16 ||| The coszen will be universal: coszen=0.7680388067483401
(Worker pid=7400) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice (Worker pid=7400) return _methods._mean(a, axis=axis, dtype=dtype, (Worker pid=7400) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide (Worker pid=7400) ret = ret.dtype.type(ret / rcount)
(Worker pid=7400) INFO:2026-05-20,21:55:17 ||| Worker 0 completed 1/~105.0:: 0.95% complete
(Worker pid=7400) INFO:2026-05-20,21:55:20 ||| Worker at start location (42,0) completed 10/11
(Worker pid=7399) ERROR:2026-05-20,21:55:17 ||| [x] Data path does not exist [repeated 3x across cluster] (Ray deduplicates logs by default. Set RAY_DEDUP_LOGS=0 to disable log deduplication, or see https://docs.ray.io/en/master/ray-observability/user-guides/configure-logging.html#log-deduplication for more options.) (Worker pid=7399) ERROR:2026-05-20,21:55:17 ||| The following path does not exist, please verify your installation environment: /home/runner/.isofit/data/earth_sun_distance.txt [repeated 3x across cluster] (Worker pid=7399) WARNING:2026-05-20,21:55:17 ||| Earth-sun-distance file not found on system. Proceeding without might cause some inaccuracies down the line. [repeated 3x across cluster] (Worker pid=7400) INFO:2026-05-20,21:55:21 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 61x across cluster]
(Worker pid=7399) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice [repeated 3x across cluster] (Worker pid=7399) return _methods._mean(a, axis=axis, dtype=dtype, [repeated 3x across cluster] (Worker pid=7399) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide [repeated 3x across cluster] (Worker pid=7399) ret = ret.dtype.type(ret / rcount) [repeated 3x across cluster]
(Worker pid=7399) INFO:2026-05-20,21:55:21 ||| Worker 3 completed 11/~105.0:: 10.48% complete [repeated 7x across cluster]
(Worker pid=7399) INFO:2026-05-20,21:55:23 ||| Worker at start location (85,0) completed 10/11 [repeated 7x across cluster]
(Worker pid=7400) INFO:2026-05-20,21:55:27 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 73x across cluster]
(Worker pid=7401) INFO:2026-05-20,21:55:27 ||| Worker 2 completed 34/~105.0:: 32.38% complete [repeated 8x across cluster]
(Worker pid=7401) INFO:2026-05-20,21:55:30 ||| Worker at start location (171,0) completed 10/11 [repeated 8x across cluster]
(Worker pid=7398) INFO:2026-05-20,21:55:32 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 72x across cluster]
(Worker pid=7399) INFO:2026-05-20,21:55:30 ||| Worker 3 completed 44/~105.0:: 41.9% complete [repeated 4x across cluster]
(Worker pid=7398) INFO:2026-05-20,21:55:35 ||| Worker at start location (225,0) completed 10/11 [repeated 6x across cluster]
(Worker pid=7399) INFO:2026-05-20,21:55:37 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 69x across cluster]
(Worker pid=7399) INFO:2026-05-20,21:55:36 ||| Worker 3 completed 66/~105.0:: 62.86% complete [repeated 8x across cluster]
(Worker pid=7399) INFO:2026-05-20,21:55:39 ||| Worker at start location (300,0) completed 10/11 [repeated 6x across cluster]
(Worker pid=7401) INFO:2026-05-20,21:55:42 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 67x across cluster]
(Worker pid=7398) INFO:2026-05-20,21:55:42 ||| Worker 1 completed 87/~105.0:: 82.86% complete [repeated 6x across cluster]
(Worker pid=7398) INFO:2026-05-20,21:55:45 ||| Worker at start location (365,0) completed 10/11 [repeated 7x across cluster]
(Worker pid=7401) INFO:2026-05-20,21:55:47 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 63x across cluster]
(Worker pid=7398) INFO:2026-05-20,21:55:46 ||| Worker 1 completed 98/~105.0:: 93.33% complete [repeated 5x across cluster]
INFO:2026-05-20,21:55:48 || isofit.py:run() | Pixel class: uniform_surface inversions complete.
INFO:2026-05-20,21:55:48 || isofit.py:run() | Running totals
INFO:2026-05-20,21:55:48 || isofit.py:run() | 39.52s total
INFO:2026-05-20,21:55:48 || isofit.py:run() | 10.6277 spectra/s
INFO:2026-05-20,21:55:48 || isofit.py:run() | 2.6569 spectra/s/core
INFO:2026-05-20,21:55:48 || isofit.py:run() | All Inversions complete.
INFO:2026-05-20,21:55:48 || isofit.py:run() | Final totals
INFO:2026-05-20,21:55:48 || isofit.py:run() | 39.52s total
INFO:2026-05-20,21:55:48 || isofit.py:run() | 10.6277 spectra/s
INFO:2026-05-20,21:55:48 || isofit.py:run() | 2.6569 spectra/s/core
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | Full (non-aerosol) LUTs:
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | Elevation: None
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | To-sensor zenith: [0.9608 2.9675]
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | To-sun zenith: None
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | Relative to-sun azimuth: [3.80000e-03 4.12002e+01 8.23965e+01]
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | H2O Vapor: [0.703 0.7574]
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | Writing main configuration file.
INFO:2026-05-20,21:55:48 || template_construction.py:load_climatology() | Loading Climatology
INFO:2026-05-20,21:55:48 || template_construction.py:load_climatology() | Climatology Loaded. Aerosol State Vector:
{'AOT550': {'bounds': [0.04209, 1.0], 'scale': 1, 'init': 0.137881, 'prior_sigma': 10.0, 'prior_mean': 0.137881}}
Aerosol LUT Grid:
{'AOT550': [0.04209, 0.137881, 0.233672, 0.329463, 0.425254, 0.521045, 0.6168359999999999, 0.712627, 0.808418, 0.9042089999999999, 1.0]}
Aerosol model path:/home/runner/work/isofit-tutorials/data/aerosol_model.txt
INFO:2026-05-20,21:55:48 || ini.py:toTemplate() | Saved converted json to: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/config/NIS01_20210403_173647_isofit.json.tmpl
INFO:2026-05-20,21:55:48 || apply_oe.py:apply_oe() | Running ISOFIT with full LUT
WARNING:2026-05-20,21:55:48 || __init__.py:checkNumThreads() | ****************************************************************************************** ! Number of threads is greater than 1 (currently: 4), this may greatly impact performance ! Please set this the environment variables 'MKL_NUM_THREADS' and 'OMP_NUM_THREADS' to '1' ******************************************************************************************
INFO:2026-05-20,21:55:48 || configs.py:create_new_config() | Loading config file: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/config/NIS01_20210403_173647_isofit.json
INFO:2026-05-20,21:55:48 || configs.py:get_config_errors() | Checking config sections for configuration issues
INFO:2026-05-20,21:55:48 || configs.py:get_config_errors() | Configuration file checks complete, no errors found.
/home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice return _methods._mean(a, axis=axis, dtype=dtype, /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide ret = ret.dtype.type(ret / rcount)
2026-05-20 21:55:48,996 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
INFO:2026-05-20,21:55:49 || isofit.py:run() | Running surfaces: uniform_surface
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:__init__() | Loading from wavelength_file: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/data/wavelengths.txt
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:__init__() | No LUT store found, beginning initialization and simulations
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:__init__() | Initializing LUT file
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:runSimulations() | Running any pre-sim functions
INFO:2026-05-20,21:55:49 || sRTMnet.py:preSim() | Creating a simulator configuration
INFO:2026-05-20,21:55:49 || sRTMnet.py:preSim() | Building simulator and executing (6S)
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:__init__() | No LUT store found, beginning initialization and simulations
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:__init__() | Initializing LUT file
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:runSimulations() | Running any pre-sim functions
INFO:2026-05-20,21:55:49 || radiative_transfer_engine.py:runSimulations() | Executing parallel simulations
INFO:2026-05-20,21:56:16 || common.py:__call__() | 10.61% simulations complete (elapsed: 0:00:26.647901, rate: 0:00:00.201878, eta: 0:03:59.831109)
INFO:2026-05-20,21:56:16 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:56:32 || common.py:__call__() | 20.45% simulations complete (elapsed: 0:00:42.239113, rate: 0:00:00.319993, eta: 0:02:48.956452)
INFO:2026-05-20,21:56:32 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:56:47 || common.py:__call__() | 30.30% simulations complete (elapsed: 0:00:57.795445, rate: 0:00:00.437844, eta: 0:02:14.856038)
INFO:2026-05-20,21:56:47 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:57:07 || common.py:__call__() | 40.15% simulations complete (elapsed: 0:01:17.723441, rate: 0:00:00.588814, eta: 0:01:56.585161)
INFO:2026-05-20,21:57:07 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:57:23 || common.py:__call__() | 50.00% simulations complete (elapsed: 0:01:33.985141, rate: 0:00:00.712009, eta: 0:01:33.985141)
INFO:2026-05-20,21:57:23 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:57:38 || common.py:__call__() | 60.61% simulations complete (elapsed: 0:01:48.650105, rate: 0:00:00.823107, eta: 0:01:12.433403)
INFO:2026-05-20,21:57:38 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:57:57 || common.py:__call__() | 70.45% simulations complete (elapsed: 0:02:08.065004, rate: 0:00:00.970189, eta: 0:00:54.885002)
INFO:2026-05-20,21:57:57 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:58:13 || common.py:__call__() | 80.30% simulations complete (elapsed: 0:02:23.657128, rate: 0:00:01.088312, eta: 0:00:35.914282)
INFO:2026-05-20,21:58:13 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:58:29 || common.py:__call__() | 90.15% simulations complete (elapsed: 0:02:39.686022, rate: 0:00:01.209743, eta: 0:00:17.742891)
INFO:2026-05-20,21:58:29 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:58:45 || common.py:__call__() | 100.00% simulations complete (elapsed: 0:02:56.003716, rate: 0:00:01.333361, eta: 0:00:00)
INFO:2026-05-20,21:58:45 || radiative_transfer_engine.py:runSimulations() | Flushing netCDF to disk
INFO:2026-05-20,21:58:45 || radiative_transfer_engine.py:runSimulations() | Running any post-sim functions
INFO:2026-05-20,21:58:46 || radiative_transfer_engine.py:runSimulations() | Saving post-sim data to index zero of all dimensions except wl
INFO:2026-05-20,21:58:46 || luts.py:load() | Loading LUT into memory
WARNING:2026-05-20,21:58:46 || luts.py:load() | thermal_upwelling is fully NaN, leaving as-is
WARNING:2026-05-20,21:58:46 || luts.py:load() | thermal_downwelling is fully NaN, leaving as-is
INFO:2026-05-20,21:58:46 || sRTMnet.py:preSim() | Interpolating simulator quantities to emulator size
INFO:2026-05-20,21:58:46 || sRTMnet.py:preSim() | Loading and predicting with emulator on 4 cores
INFO:2026-05-20,21:58:48 || radiative_transfer_engine.py:runSimulations() | Saving pre-sim data to index zero of all dimensions except wl
INFO:2026-05-20,21:58:48 || radiative_transfer_engine.py:runSimulations() | Running any post-sim functions
INFO:2026-05-20,21:58:48 || luts.py:load() | Loading LUT into memory
WARNING:2026-05-20,21:58:48 || luts.py:load() | thermal_upwelling is fully NaN, leaving as-is
WARNING:2026-05-20,21:58:48 || luts.py:load() | thermal_downwelling is fully NaN, leaving as-is
INFO:2026-05-20,21:58:49 || isofit.py:run() | Beginning 420 inversions in 40 chunks using 4 cores
(Worker pid=8216) ERROR:2026-05-20,21:58:57 ||| [x] Data path does not exist (Worker pid=8216) ERROR:2026-05-20,21:58:57 ||| The following path does not exist, please verify your installation environment: /home/runner/.isofit/data/earth_sun_distance.txt (Worker pid=8216) WARNING:2026-05-20,21:58:57 ||| Earth-sun-distance file not found on system. Proceeding without might cause some inaccuracies down the line. (Worker pid=7398) INFO:2026-05-20,21:55:48 ||| Worker at start location (408,0) completed 10/11 [repeated 4x across cluster] (Worker pid=7398) INFO:2026-05-20,21:55:48 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 14x across cluster]
(Worker pid=8216) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice (Worker pid=8216) return _methods._mean(a, axis=axis, dtype=dtype, (Worker pid=8216) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide (Worker pid=8216) ret = ret.dtype.type(ret / rcount)
(Worker pid=8216) INFO:2026-05-20,21:58:59 ||| Worker 2 completed 1/~105.0:: 0.95% complete
(Worker pid=8217) ERROR:2026-05-20,21:58:57 ||| [x] Data path does not exist [repeated 3x across cluster] (Worker pid=8217) ERROR:2026-05-20,21:58:57 ||| The following path does not exist, please verify your installation environment: /home/runner/.isofit/data/earth_sun_distance.txt [repeated 3x across cluster] (Worker pid=8217) WARNING:2026-05-20,21:58:57 ||| Earth-sun-distance file not found on system. Proceeding without might cause some inaccuracies down the line. [repeated 3x across cluster] (Worker pid=8214) INFO:2026-05-20,21:59:02 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 34x across cluster]
(Worker pid=8216) INFO:2026-05-20,21:59:03 ||| Worker at start location (20,0) completed 10/11
(Worker pid=8217) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice [repeated 3x across cluster] (Worker pid=8217) return _methods._mean(a, axis=axis, dtype=dtype, [repeated 3x across cluster] (Worker pid=8217) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide [repeated 3x across cluster] (Worker pid=8217) ret = ret.dtype.type(ret / rcount) [repeated 3x across cluster]
(Worker pid=8216) INFO:2026-05-20,21:59:04 ||| Worker 2 completed 12/~105.0:: 11.43% complete [repeated 5x across cluster]
(Worker pid=8217) INFO:2026-05-20,21:59:07 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 47x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:08 ||| Worker at start location (63,0) completed 10/11 [repeated 7x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:08 ||| Worker 3 completed 22/~105.0:: 20.95% complete [repeated 6x across cluster]
(Worker pid=8217) INFO:2026-05-20,21:59:12 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 55x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:12 ||| Worker at start location (128,0) completed 10/11 [repeated 4x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:13 ||| Worker 3 completed 33/~105.0:: 31.43% complete [repeated 4x across cluster]
(Worker pid=8214) INFO:2026-05-20,21:59:17 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 48x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:17 ||| Worker at start location (171,0) completed 10/11 [repeated 4x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:18 ||| Worker 3 completed 44/~105.0:: 41.9% complete [repeated 4x across cluster]
(Worker pid=8214) INFO:2026-05-20,21:59:22 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 46x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:22 ||| Worker at start location (214,0) completed 10/11 [repeated 4x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:22 ||| Worker 3 completed 55/~105.0:: 52.38% complete [repeated 4x across cluster]
(Worker pid=8214) INFO:2026-05-20,21:59:28 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 42x across cluster]
(Worker pid=8217) INFO:2026-05-20,21:59:27 ||| Worker at start location (246,0) completed 10/11 [repeated 4x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:28 ||| Worker 3 completed 66/~105.0:: 62.86% complete [repeated 4x across cluster]
(Worker pid=8214) INFO:2026-05-20,21:59:33 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 46x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:32 ||| Worker at start location (289,0) completed 9/10 [repeated 4x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:33 ||| Worker 3 completed 76/~105.0:: 72.38% complete [repeated 4x across cluster]
(Worker pid=8216) INFO:2026-05-20,21:59:38 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 47x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:37 ||| Worker at start location (343,0) completed 10/11 [repeated 4x across cluster]
(Worker pid=8215) INFO:2026-05-20,21:59:37 ||| Worker 3 completed 87/~105.0:: 82.86% complete [repeated 4x across cluster]
(Worker pid=8216) INFO:2026-05-20,21:59:43 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 45x across cluster]
(Worker pid=8216) INFO:2026-05-20,21:59:44 ||| Worker at start location (397,0) completed 10/11 [repeated 5x across cluster]
(Worker pid=8217) INFO:2026-05-20,21:59:42 ||| Worker 1 completed 98/~105.0:: 93.33% complete [repeated 3x across cluster]
INFO:2026-05-20,21:59:45 || isofit.py:run() | Pixel class: uniform_surface inversions complete.
INFO:2026-05-20,21:59:45 || isofit.py:run() | Running totals
INFO:2026-05-20,21:59:45 || isofit.py:run() | 55.8s total
INFO:2026-05-20,21:59:45 || isofit.py:run() | 7.5272 spectra/s
INFO:2026-05-20,21:59:45 || isofit.py:run() | 1.8818 spectra/s/core
INFO:2026-05-20,21:59:45 || isofit.py:run() | All Inversions complete.
INFO:2026-05-20,21:59:45 || isofit.py:run() | Final totals
INFO:2026-05-20,21:59:45 || isofit.py:run() | 55.8s total
INFO:2026-05-20,21:59:45 || isofit.py:run() | 7.5272 spectra/s
INFO:2026-05-20,21:59:45 || isofit.py:run() | 1.8818 spectra/s/core
INFO:2026-05-20,21:59:45 || apply_oe.py:apply_oe() | Analytical line inference
INFO:2026-05-20,21:59:45 || configs.py:create_new_config() | Loading config file: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/config/NIS01_20210403_173647_isofit.json
/home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice return _methods._mean(a, axis=axis, dtype=dtype, /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide ret = ret.dtype.type(ret / rcount)
2026-05-20 21:59:45,907 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
INFO:2026-05-20,21:59:45 || atm_interpolation.py:atm_interpolation() | Beginning atmospheric interpolation 4 cores
(_run_chunk pid=8357) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/isofit/utils/atm_interpolation.py:182: LinAlgWarning: An ill-conditioned matrix detected: slice 0 has rcond = 5.806051187360241e-33. (_run_chunk pid=8357) bhat[i, :] = (inv(X.T @ W @ X) @ X.T @ W @ y).T (Worker pid=8217) INFO:2026-05-20,21:59:45 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 10x across cluster] (Worker pid=8217) INFO:2026-05-20,21:59:45 ||| Worker at start location (419,0) completed 10/11 [repeated 2x across cluster]
INFO:2026-05-20,21:59:52 || atm_interpolation.py:atm_interpolation() | Parallel atmospheric interpolations complete. 6.582397699356079 s total, 641.2556932579321 spectra/s, 160.31392331448302 spectra/s/core
2026-05-20 21:59:52,519 INFO worker.py:1828 -- Calling ray.init() again after it has already been called.
INFO:2026-05-20,21:59:52 || radiative_transfer_engine.py:__init__() | Loading from wavelength_file: /home/runner/work/isofit-tutorials/isofit-tutorials/NEON/output/NIS01_20210403_173647/data/wavelengths.txt
INFO:2026-05-20,21:59:52 || radiative_transfer_engine.py:__init__() | Prebuilt LUT provided
INFO:2026-05-20,21:59:52 || luts.py:load() | Loading LUT into memory
WARNING:2026-05-20,21:59:52 || luts.py:load() | thermal_upwelling is fully NaN, leaving as-is
WARNING:2026-05-20,21:59:52 || luts.py:load() | thermal_downwelling is fully NaN, leaving as-is
INFO:2026-05-20,21:59:52 || radiative_transfer_engine.py:__init__() | LUT grid loaded from file
(Worker pid=8607) ERROR:2026-05-20,22:00:00 ||| [x] Data path does not exist (Worker pid=8607) ERROR:2026-05-20,22:00:00 ||| The following path does not exist, please verify your installation environment: /home/runner/.isofit/data/earth_sun_distance.txt (Worker pid=8607) WARNING:2026-05-20,22:00:00 ||| Earth-sun-distance file not found on system. Proceeding without might cause some inaccuracies down the line. (_run_chunk pid=8359) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/isofit/utils/atm_interpolation.py:182: LinAlgWarning: An ill-conditioned matrix detected: slice 0 has rcond = 5.806051187360241e-33. [repeated 3x across cluster] (_run_chunk pid=8359) bhat[i, :] = (inv(X.T @ W @ X) @ X.T @ W @ y).T [repeated 3x across cluster] (Worker pid=8607) INFO:2026-05-20,22:00:00 ||| The coszen will be universal: coszen=0.7680388067483401 (Worker pid=8608) INFO:2026-05-20,22:00:00 ||| The coszen will be universal: coszen=0.7680388067483401
(Worker pid=8607) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice (Worker pid=8607) return _methods._mean(a, axis=axis, dtype=dtype, (Worker pid=8607) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide (Worker pid=8607) ret = ret.dtype.type(ret / rcount)
(Worker pid=8607) INFO:2026-05-20,22:00:04 ||| Analytical line writing lines: 3 to 4. Surface: uniform_surface
(Worker pid=8606) ERROR:2026-05-20,22:00:00 ||| [x] Data path does not exist [repeated 3x across cluster] (Worker pid=8606) ERROR:2026-05-20,22:00:00 ||| The following path does not exist, please verify your installation environment: /home/runner/.isofit/data/earth_sun_distance.txt [repeated 3x across cluster] (Worker pid=8606) WARNING:2026-05-20,22:00:00 ||| Earth-sun-distance file not found on system. Proceeding without might cause some inaccuracies down the line. [repeated 3x across cluster] (Worker pid=8606) INFO:2026-05-20,22:00:05 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 346x across cluster]
(Worker pid=8606) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:3824: RuntimeWarning: Mean of empty slice [repeated 3x across cluster] (Worker pid=8606) return _methods._mean(a, axis=axis, dtype=dtype, [repeated 3x across cluster] (Worker pid=8606) /home/runner/work/isofit-tutorials/isofit-tutorials/.venv/lib/python3.12/site-packages/numpy/_core/_methods.py:142: RuntimeWarning: invalid value encountered in scalar divide [repeated 3x across cluster] (Worker pid=8606) ret = ret.dtype.type(ret / rcount) [repeated 3x across cluster]
(Worker pid=8609) INFO:2026-05-20,22:00:08 ||| Analytical line writing lines: 1 to 3. Surface: uniform_surface [repeated 4x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:10 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 377x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:11 ||| Analytical line writing lines: 11 to 12. Surface: uniform_surface [repeated 2x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:15 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 376x across cluster]
(Worker pid=8607) INFO:2026-05-20,22:00:18 ||| Analytical line writing lines: 14 to 16. Surface: uniform_surface [repeated 4x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:20 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 374x across cluster]
(Worker pid=8609) INFO:2026-05-20,22:00:23 ||| Analytical line writing lines: 24 to 25. Surface: uniform_surface [repeated 4x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:25 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 376x across cluster]
(Worker pid=8608) INFO:2026-05-20,22:00:29 ||| Analytical line writing lines: 25 to 27. Surface: uniform_surface [repeated 3x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:30 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 374x across cluster]
(Worker pid=8607) INFO:2026-05-20,22:00:32 ||| Analytical line writing lines: 30 to 32. Surface: uniform_surface [repeated 3x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:35 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 375x across cluster]
(Worker pid=8607) INFO:2026-05-20,22:00:39 ||| Analytical line writing lines: 38 to 40. Surface: uniform_surface [repeated 4x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:40 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 376x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:44 ||| Analytical line writing lines: 43 to 45. Surface: uniform_surface [repeated 4x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:45 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 374x across cluster]
(Worker pid=8609) INFO:2026-05-20,22:00:48 ||| Analytical line writing lines: 48 to 50. Surface: uniform_surface [repeated 3x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:50 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 367x across cluster]
(Worker pid=8608) INFO:2026-05-20,22:00:54 ||| Analytical line writing lines: 54 to 56. Surface: uniform_surface [repeated 4x across cluster]
(Worker pid=8606) INFO:2026-05-20,22:00:56 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 356x across cluster]
INFO:2026-05-20,22:00:59 || analytical_line.py:analytical_line() | Analytical line inversions complete. 66.17s total, 63.791 spectra/s, 15.9477 spectra/s/core
INFO:2026-05-20,22:00:59 || apply_oe.py:apply_oe() | Done.
(Worker pid=8607) INFO:2026-05-20,22:00:59 ||| Analytical line writing lines: 61 to 63. Surface: uniform_surface [repeated 3x across cluster] (Worker pid=8607) INFO:2026-05-20,22:00:59 ||| The coszen will be universal: coszen=0.7680388067483401 [repeated 148x across cluster]
Plotting¶
Below plots the regions of interest defined by a NEON report.
# Load in the ISOFIT reflectance output
ds = envi.open(paths.working / f"output/{neon_str}_rfl.hdr")
rfl = ds.open_memmap(interleave='bip')
rgb = rfl[:, :, [60, 40, 30]].copy()
wl = np.array(ds.metadata['wavelength'], dtype=float)
# Find the bounding box for all regions of interest (RoI)
regions = report[neon_id]
bounds = np.vstack(list(regions.values()))
y = bounds[:, 0].min() - 5 # , bounds[:, 1].max() + 5
x = bounds[:, 2].min() - 5 # , bounds[:, 3].max() + 5
# Plot the RoIs
fig, ax = plt.subplots(figsize=(7, 7))
ax.imshow(rgb / np.max(rgb, axis=(0, 1))) # Dividing brightens the image
ax.set_title(neon_str)
for i, (roi, region) in enumerate(regions.items()):
rect = patches.Rectangle(
(region[2] - x, region[0] - y),
region[3] - region[2],
region[1] - region[0],
linewidth = 1,
edgecolor = f'C{i}',
facecolor = 'none',
label = roi
)
ax.add_patch(rect)
ax.legend(loc='lower right')
<matplotlib.legend.Legend at 0x7f628c1769c0>
fig, axes = plt.subplots(len(regions), sharex=True, figsize=(10, 3*len(regions)))
for i, (roi, region) in enumerate(regions.items()):
ax = axes[i]
in_situ = np.genfromtxt(paths.insitu / f'{roi}01/Data/{roi}01_Refl.dat', skip_header=3)
ax.plot(in_situ[:, 0], in_situ[:, 1], label='In Situ', c='red', ls='-')
mean_rfl = np.mean(
rfl[
region[0] - y : region[1] - y,
region[2] - x : region[3] - x,
],
axis = (0, 1)
)
ax.plot(wl, mean_rfl, label='Isofit', c='black')
ax.set_ylabel('Reflectance')
ax.set_title(roi)
ax.legend()
ax.set_xlabel('Wavelength')
plt.tight_layout()
We can plot out the mapped reflectance (as above), but also the interpolated atmospheric conditions. The windows size is small enough here (and the atmospheric parameters are chosen in such a way) that the map is going to be pretty static...but we can still see it.
dat = envi.open(paths.working / f"output/{neon_str}_atm_interp.hdr")
atm = dat.open_memmap(interleave='bip').copy()
plt.figure(figsize=(7, 7))
plt.title('AOD')
plt.imshow(atm[..., 0])
plt.colorbar()
plt.figure(figsize=(7, 7))
plt.title('Water Vapor')
plt.imshow(atm[..., 1])
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7f62d867bd10>