Skip to content

Commit f928c91

Browse files
Add wavelength selection support for Tanager (#157)
* Add wavelength selection support for Tanager * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 55df39f commit f928c91

File tree

3 files changed

+85
-39
lines changed

3 files changed

+85
-39
lines changed

hypercoast/hypercoast.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,7 @@ def add_tanager(
757757
self,
758758
source,
759759
bands=None,
760+
wavelengths=None,
760761
indexes=None,
761762
colormap=None,
762763
vmin=0,
@@ -784,8 +785,8 @@ def add_tanager(
784785
Args:
785786
source (str): The path to the GeoTIFF file or the URL of the Cloud
786787
Optimized GeoTIFF.
787-
indexes (int, optional): The band(s) to use. Band indexing starts
788-
at 1. Defaults to None.
788+
bands (list, optional): The band indices to select. Defaults to None.
789+
wavelengths (list, optional): The wavelength values to select. Takes priority over bands. Defaults to None.
789790
colormap (str, optional): The name of the colormap from `matplotlib`
790791
to use when plotting a single band. See
791792
https://matplotlib.org/stable/gallery/color/colormap_reference.html.
@@ -809,18 +810,34 @@ def add_tanager(
809810
if array_args is None:
810811
array_args = {}
811812

812-
if "wavelength" in kwargs:
813-
bands = kwargs["wavelength"]
814-
kwargs.pop("wavelength")
815-
816813
if isinstance(source, str):
817814

818815
source = read_tanager(source)
819816

817+
selected_wavelengths = []
818+
if wavelengths is not None:
819+
selected_wavelengths = wavelengths
820+
elif bands is not None:
821+
for band in bands:
822+
if isinstance(band, (int, np.integer)) or (
823+
isinstance(band, float) and band < 500
824+
):
825+
# Treat as band index
826+
selected_wavelengths.append(
827+
source.coords["wavelength"].values[int(band)]
828+
)
829+
else:
830+
# Treat as wavelength value
831+
selected_wavelengths.append(band)
832+
833+
else:
834+
selected_wavelengths = [876.3, 675.88, 625.83]
820835
try:
821-
image = tanager_to_image(source, bands=bands, method=method)
836+
image = tanager_to_image(
837+
source, wavelengths=selected_wavelengths, method=method
838+
)
822839

823-
if isinstance(bands, list) and len(bands) > 1:
840+
if isinstance(selected_wavelengths, list) and len(selected_wavelengths) > 1:
824841
colormap = None
825842

826843
self.add_raster(
@@ -842,7 +859,7 @@ def add_tanager(
842859
self.cog_layer_dict[layer_name]["vmax"] = vmax
843860
self.cog_layer_dict[layer_name]["vmin"] = vmin
844861
self.cog_layer_dict[layer_name]["hyper"] = "TANAGER"
845-
self._update_band_names(layer_name, bands)
862+
self._update_band_names(layer_name, selected_wavelengths)
846863
except Exception as e:
847864
print(e)
848865

hypercoast/tanager.py

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,21 @@ def read_tanager(filepath, bands=None, stac_url=None, **kwargs):
5050
data = data[bands]
5151

5252
coords = {
53-
"band": np.arange(data.shape[0]),
54-
"wavelength": ("band", wavelengths),
55-
"fwhm": ("band", fwhm),
53+
"wavelength": wavelengths,
54+
"fwhm": ("wavelength", fwhm),
5655
"latitude": (("y", "x"), lat),
5756
"longitude": (("y", "x"), lon),
5857
}
5958

60-
da = xr.DataArray(data, dims=("band", "y", "x"), coords=coords, name="toa_radiance")
59+
da = xr.DataArray(
60+
data, dims=("wavelength", "y", "x"), coords=coords, name="toa_radiance"
61+
)
6162

6263
ds = xr.Dataset(
6364
data_vars={"toa_radiance": da},
6465
coords={
65-
"band": da.band,
66-
"wavelength": ("band", wavelengths),
67-
"fwhm": ("band", fwhm),
66+
"wavelength": da.wavelength,
67+
"fwhm": ("wavelength", fwhm),
6868
"latitude": (("y", "x"), lat),
6969
"longitude": (("y", "x"), lon),
7070
},
@@ -78,13 +78,14 @@ def read_tanager(filepath, bands=None, stac_url=None, **kwargs):
7878
return ds
7979

8080

81-
def grid_tanager(dataset, bands=None, method="nearest", **kwargs):
81+
def grid_tanager(dataset, bands=None, wavelengths=None, method="nearest", **kwargs):
8282
"""
8383
Grids a Tanager dataset based on latitude and longitude.
8484
8585
Args:
8686
dataset (xr.Dataset): The Tanager dataset to grid.
87-
bands (list): The bands to select.
87+
bands (list, optional): The band indices to select. Defaults to None.
88+
wavelengths (list, optional): The wavelength values to select. Takes priority over bands. Defaults to None.
8889
method (str, optional): The method to use for griddata interpolation.
8990
Defaults to "nearest".
9091
**kwargs: Additional keyword arguments to pass to the xr.Dataset constructor.
@@ -95,26 +96,52 @@ def grid_tanager(dataset, bands=None, method="nearest", **kwargs):
9596
from scipy.interpolate import griddata
9697
from scipy.spatial import ConvexHull
9798

98-
if bands is None:
99-
bands = dataset.coords["band"].values[0]
100-
101-
# Ensure wavelengths is a list
102-
if not isinstance(bands, list):
103-
bands = [bands]
99+
# Priority: wavelengths > bands > default
100+
if wavelengths is not None:
101+
# Use wavelengths directly
102+
if not isinstance(wavelengths, list):
103+
wavelengths = [wavelengths]
104+
selected_wavelengths = wavelengths
105+
elif bands is not None:
106+
# Convert bands to wavelengths
107+
if not isinstance(bands, list):
108+
bands = [bands]
109+
110+
selected_wavelengths = []
111+
for band in bands:
112+
if isinstance(band, (int, np.integer)) or (
113+
isinstance(band, float) and band < 500
114+
):
115+
# Treat as band index
116+
selected_wavelengths.append(
117+
dataset.coords["wavelength"].values[int(band)]
118+
)
119+
else:
120+
# Treat as wavelength value
121+
selected_wavelengths.append(band)
122+
else:
123+
# Default to first wavelength
124+
selected_wavelengths = [dataset.coords["wavelength"].values[0]]
104125

105126
lat = dataset.latitude
106127
lon = dataset.longitude
107128

108-
# Find valid data points for any band to define spatial mask
109-
first_band_data = dataset.sel(band=bands[0], method="nearest")["toa_radiance"]
110-
overall_valid_mask = ~np.isnan(first_band_data.data) & (first_band_data.data > 0)
129+
# Find valid data points for any wavelength to define spatial mask
130+
first_wavelength_data = dataset.sel(
131+
wavelength=selected_wavelengths[0], method="nearest"
132+
)["toa_radiance"]
133+
overall_valid_mask = ~np.isnan(first_wavelength_data.data) & (
134+
first_wavelength_data.data > 0
135+
)
111136

112137
if not np.any(overall_valid_mask):
113138
# No valid data, return empty grid
114139
grid_lat = np.linspace(lat.min().values, lat.max().values, lat.shape[0])
115140
grid_lon = np.linspace(lon.min().values, lon.max().values, lon.shape[1])
116141
grid_lon_2d, grid_lat_2d = np.meshgrid(grid_lon, grid_lat)
117-
gridded_data_dict = {band: np.full_like(grid_lat_2d, np.nan) for band in bands}
142+
gridded_data_dict = {
143+
wl: np.full_like(grid_lat_2d, np.nan) for wl in selected_wavelengths
144+
}
118145
else:
119146
# Get valid coordinates for spatial masking
120147
valid_lat = lat.data[overall_valid_mask]
@@ -148,8 +175,8 @@ def grid_tanager(dataset, bands=None, method="nearest", **kwargs):
148175
)
149176

150177
gridded_data_dict = {}
151-
for band in bands:
152-
data = dataset.sel(band=band, method="nearest")["toa_radiance"]
178+
for wl in selected_wavelengths:
179+
data = dataset.sel(wavelength=wl, method="nearest")["toa_radiance"]
153180

154181
# Mask nodata values (both NaN and zero values)
155182
data_flat = data.data.flatten()
@@ -166,19 +193,18 @@ def grid_tanager(dataset, bands=None, method="nearest", **kwargs):
166193
)
167194
# Apply spatial mask to prevent edge interpolation
168195
gridded_data[~inside_hull] = np.nan
169-
gridded_data_dict[band] = gridded_data
196+
gridded_data_dict[wl] = gridded_data
170197

171-
wavelengths = dataset.wavelength.values[bands]
198+
selected_wavelengths = list(gridded_data_dict.keys())
172199
# Create a 3D array with dimensions latitude, longitude, and wavelength
173200
gridded_data_3d = np.dstack(list(gridded_data_dict.values()))
174201

175202
dataset2 = xr.Dataset(
176-
{"toa_radiance": (("latitude", "longitude", "band"), gridded_data_3d)},
203+
{"toa_radiance": (("latitude", "longitude", "wavelength"), gridded_data_3d)},
177204
coords={
178205
"latitude": ("latitude", grid_lat),
179206
"longitude": ("longitude", grid_lon),
180-
"band": ("band", list(gridded_data_dict.keys())),
181-
"wavelength": ("band", wavelengths),
207+
"wavelength": ("wavelength", selected_wavelengths),
182208
},
183209
**kwargs,
184210
)
@@ -188,13 +214,16 @@ def grid_tanager(dataset, bands=None, method="nearest", **kwargs):
188214
return dataset2
189215

190216

191-
def tanager_to_image(dataset, bands=None, method="nearest", output=None, **kwargs):
217+
def tanager_to_image(
218+
dataset, bands=None, wavelengths=None, method="nearest", output=None, **kwargs
219+
):
192220
"""
193221
Converts an Tanager dataset to an image.
194222
195223
Args:
196224
dataset (xarray.Dataset or str): The dataset containing the EMIT data or the file path to the dataset.
197-
bands (array-like, optional): The specific bands to select. If None, all bands are selected. Defaults to None.
225+
bands (array-like, optional): The specific band indices to select. Defaults to None.
226+
wavelengths (array-like, optional): The specific wavelength values to select. Takes priority over bands. Defaults to None.
198227
method (str, optional): The method to use for data interpolation. Defaults to "nearest".
199228
output (str, optional): The file path where the image will be saved. If None, the image will be returned as a PIL Image object. Defaults to None.
200229
**kwargs: Additional keyword arguments to be passed to `leafmap.array_to_image`.
@@ -207,7 +236,7 @@ def tanager_to_image(dataset, bands=None, method="nearest", output=None, **kwarg
207236
if isinstance(dataset, str):
208237
dataset = read_tanager(dataset, bands=bands)
209238

210-
grid = grid_tanager(dataset, bands=bands, method=method)
239+
grid = grid_tanager(dataset, bands=bands, wavelengths=wavelengths, method=method)
211240

212241
data = grid["toa_radiance"]
213242
data.rio.write_crs("EPSG:4326", inplace=True)

hypercoast/ui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ def handle_interaction(**kwargs):
221221
"wavelength"
222222
].values
223223

224-
da = da.swap_dims({"band": "wavelength"})
224+
# da = da.swap_dims({"band": "wavelength"})
225225
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "PACE":
226226
try:
227227
da = extract_pace(ds, lat, lon)

0 commit comments

Comments
 (0)