diff --git a/hypercoast/aviris.py b/hypercoast/aviris.py index 62c180bf..aa9d9220 100644 --- a/hypercoast/aviris.py +++ b/hypercoast/aviris.py @@ -19,7 +19,7 @@ import rioxarray import numpy as np import xarray as xr -from typing import List, Union, Dict, Optional, Tuple, Any +from typing import List, Union, Optional, Any from .common import convert_coords @@ -59,7 +59,7 @@ def read_aviris( geo_transform = list(rio_transform)[:6] # get the raster geotransform as its component parts - xres, xrot, xmin, yrot, yres, ymax = geo_transform + xres, _, xmin, _, yres, ymax = geo_transform # generate coordinate arrays xarr = np.array([xmin + i * xres for i in range(0, cols)]) diff --git a/hypercoast/common.py b/hypercoast/common.py index e33396e8..240c3797 100644 --- a/hypercoast/common.py +++ b/hypercoast/common.py @@ -34,7 +34,7 @@ def download_file( speed: Optional[float] = None, use_cookies: Optional[bool] = True, verify: Optional[bool] = True, - id: Optional[str] = None, + uid: Optional[str] = None, fuzzy: Optional[bool] = False, resume: Optional[bool] = False, unzip: Optional[bool] = True, @@ -52,7 +52,7 @@ def download_file( use_cookies (bool, optional): Flag to use cookies. Defaults to True. verify (bool | str, optional): Either a bool, in which case it controls whether the server's TLS certificate is verified, or a string, in which case it must be a path to a CA bundle to use. Default is True.. Defaults to True. - id (str, optional): Google Drive's file ID. Defaults to None. + uid (str, optional): Google Drive's file ID. Defaults to None. fuzzy (bool, optional): Fuzzy extraction of Google Drive's file Id. Defaults to False. resume (bool, optional): Resume the download from existing tmp file if possible. Defaults to False. unzip (bool, optional): Unzip the file. Defaults to True. @@ -87,7 +87,7 @@ def download_file( fuzzy = True output = gdown.download( - url, output, quiet, proxy, speed, use_cookies, verify, id, fuzzy, resume + url, output, quiet, proxy, speed, use_cookies, verify, uid, fuzzy, resume ) if unzip: @@ -511,9 +511,9 @@ def image_cube( rgb_gamma: float = 1.0, rgb_cmap: Optional[str] = None, rgb_clim: Optional[Tuple[float, float]] = None, - rgb_args: Dict[str, Any] = {}, + rgb_args: Dict[str, Any] = None, widget=None, - plotter_args: Dict[str, Any] = {}, + plotter_args: Dict[str, Any] = None, show_axes: bool = True, grid_origin=(0, 0, 0), grid_spacing=(1, 1, 1), @@ -558,7 +558,12 @@ def image_cube( """ import pyvista as pv - import xarray as xr + + if rgb_args is None: + rgb_args = {} + + if plotter_args is None: + plotter_args = {} allowed_widgets = ["box", "plane", "slice", "orthogonal", "threshold"] @@ -703,7 +708,7 @@ def open_dataset( try: dataset = xr.open_dataset(filename, engine=engine, chunks=chunks, **kwargs) - except Exception as e: + except OSError: dataset = xr.open_dataset(filename, engine="h5netcdf", chunks=chunks, **kwargs) return dataset @@ -806,7 +811,8 @@ def download_acolite(outdir: str = ".", platform: Optional[str] = None) -> str: download_url = base_url + "acolite_py_win_20231023.0.tar.gz" root_dir = "acolite_py_win" else: - raise Exception(f"Unsupported OS platform: {platform}") + print(f"Unsupported OS platform: {platform}") + return if not os.path.exists(outdir): os.makedirs(outdir) @@ -818,7 +824,7 @@ def download_acolite(outdir: str = ".", platform: Optional[str] = None) -> str: print(f"{file_name} already exists. Skip downloading.") return extracted_path - response = requests.get(download_url, stream=True) + response = requests.get(download_url, stream=True, timeout=60) total_size = int(response.headers.get("content-length", 0)) block_size = 8192 @@ -836,7 +842,8 @@ def download_acolite(outdir: str = ".", platform: Optional[str] = None) -> str: bar.update(len(chunk)) print(f"Downloaded {file_name}") else: - raise Exception(f"Failed to download file from {download_url}") + print(f"Failed to download file from {download_url}") + return # Unzip the file with tarfile.open(file_name, "r:gz") as tar: @@ -947,15 +954,18 @@ def get_formatted_current_time(format_str="%Y-%m-%d %H:%M:%S"): lines.append(f"runid={get_formatted_current_time('%Y%m%d_%H%M%S')}") settings_filename = f"acolite_run_{get_formatted_current_time('%Y%m%d_%H%M%S')}_settings_user.txt" settings_file = os.path.join(out_dir, settings_filename) - with open(settings_file, "w") as f: + with open(settings_file, "w", encoding="utf-8") as f: f.write("\n".join(lines)) acolite_cmd.extend(["--settings", settings_file]) if verbose: - subprocess.run(acolite_cmd) + subprocess.run(acolite_cmd, check=True) else: subprocess.run( - acolite_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + acolite_cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, ) @@ -991,8 +1001,8 @@ def perform_pca(image, n_components=3, **kwargs): image_reshaped = image.reshape(n_bands, width * height).T # Perform PCA - pca = PCA(n_components=n_components, **kwargs) - principal_components = pca.fit_transform(image_reshaped) + model = PCA(n_components=n_components, **kwargs) + principal_components = model.fit_transform(image_reshaped) # Reshape the principal components back to image dimensions pca_image = principal_components.T.reshape(n_components, width, height) @@ -1070,7 +1080,7 @@ def show_field_data( # Function to create the chart def create_chart(data, title): - fig, ax = plt.subplots(figsize=(10, 6)) # Adjust the figure size here + _, ax = plt.subplots(figsize=(10, 6)) # Adjust the figure size here ax.plot(data[x_col], data["values"]) ax.set_title(title) ax.set_xlabel(x_label) diff --git a/hypercoast/desis.py b/hypercoast/desis.py index 871773bc..992e0056 100644 --- a/hypercoast/desis.py +++ b/hypercoast/desis.py @@ -7,7 +7,6 @@ """ import rioxarray -import numpy as np import xarray as xr import pandas as pd from .common import convert_coords diff --git a/hypercoast/hypercoast.py b/hypercoast/hypercoast.py index e71bf3c1..a7b4cb07 100644 --- a/hypercoast/hypercoast.py +++ b/hypercoast/hypercoast.py @@ -8,13 +8,55 @@ import leafmap import xarray as xr import numpy as np -from .aviris import * -from .common import * -from .desis import * -from .emit import * -from .neon import * -from .pace import * +from typing import Union +from .aviris import aviris_to_image, read_aviris, extract_aviris +from .desis import desis_to_image, read_desis, extract_desis, filter_desis +from .emit import ( + emit_to_image, + read_emit, + plot_emit, + viz_emit, + emit_to_netcdf, + emit_to_image, +) +from .neon import neon_to_image, read_neon +from .pace import ( + pace_to_image, + read_pace, + read_pace_aop, + read_pace_bgc, + read_pace_chla, + view_pace_pixel_locations, + viz_pace, + viz_pace_chla, + filter_pace, + extract_pace, + grid_pace, + grid_pace_bgc, + pace_to_image, + pace_chla_to_image, +) from .ui import SpectralWidget +from .common import ( + download_file, + search_datasets, + search_nasa_data, + download_nasa_data, + search_pace, + search_pace_chla, + search_emit, + search_ecostress, + download_pace, + download_emit, + download_ecostress, + nasa_earth_login, + image_cube, + open_dataset, + download_acolite, + run_acolite, + pca, + show_field_data, +) class Map(leafmap.Map): @@ -38,6 +80,9 @@ def __init__(self, **kwargs): class's constructor. """ super().__init__(**kwargs) + self._spectral_data = {} + self._plot_options = None + self._plot_marker_cluster = None def add(self, obj, position="topright", xlim=None, ylim=None, **kwargs): """Add a layer to the map. @@ -107,10 +152,13 @@ def add_raster( nodata=None, attribution=None, layer_name="Raster", + layer_index=None, zoom_to_layer=True, visible=True, - array_args={}, - open_args={}, + opacity=1.0, + array_args=None, + client_args={"cors_all": False}, + open_args=None, **kwargs, ): """Add a local raster dataset to the map. @@ -142,17 +190,28 @@ def add_raster( attribution (str, optional): Attribution for the source raster. This defaults to a message about it being a local file.. Defaults to None. layer_name (str, optional): The layer name to use. Defaults to 'Raster'. + layer_index (int, optional): The index of the layer. Defaults to None. zoom_to_layer (bool, optional): Whether to zoom to the extent of the layer. Defaults to True. visible (bool, optional): Whether the layer is visible. Defaults to True. + opacity (float, optional): The opacity of the layer. Defaults to 1.0. array_args (dict, optional): Additional arguments to pass to `array_to_memory_file` when reading the raster. Defaults to {}. + client_args (dict, optional): Additional arguments to pass to + localtileserver.TileClient. Defaults to { "cors_all": False }. + open_args (dict, optional): Additional arguments to pass to + rioxarray.open_rasterio. + """ - import numpy as np import rioxarray as rxr + if array_args is None: + array_args = {} + if open_args is None: + open_args = {} + if nodata is None: nodata = np.nan super().add_raster( @@ -164,9 +223,12 @@ def add_raster( nodata=nodata, attribution=attribution, layer_name=layer_name, + layer_index=layer_index, zoom_to_layer=zoom_to_layer, visible=visible, + opacity=opacity, array_args=array_args, + client_args=client_args, **kwargs, ) @@ -191,13 +253,19 @@ def add_dataset( layer_name="Raster", zoom_to_layer=True, visible=True, - array_args={}, - open_args={}, + array_args=None, + open_args=None, **kwargs, ): import rioxarray as rxr from leafmap import array_to_image + if array_args is None: + array_args = {} + + if open_args is None: + open_args = {} + if isinstance(source, str): da = rxr.open_rasterio(source, **open_args) dims = da.dims @@ -260,7 +328,7 @@ def add_emit( layer_name="EMIT", zoom_to_layer=True, visible=True, - array_args={}, + array_args=None, **kwargs, ): """Add an EMIT dataset to the map. @@ -300,6 +368,9 @@ def add_emit( `array_to_memory_file` when reading the raster. Defaults to {}. """ + if array_args is None: + array_args = {} + xds = None if isinstance(source, str): @@ -343,7 +414,7 @@ def add_pace( visible=True, method="nearest", gridded=False, - array_args={}, + array_args=None, **kwargs, ): """Add a PACE dataset to the map. @@ -382,6 +453,9 @@ def add_pace( `array_to_memory_file` when reading the raster. Defaults to {}. """ + if array_args is None: + array_args = {} + if isinstance(source, str): source = read_pace(source) @@ -426,7 +500,7 @@ def add_desis( zoom_to_layer=True, visible=True, method="nearest", - array_args={}, + array_args=None, **kwargs, ): """Add a DESIS dataset to the map. @@ -464,6 +538,8 @@ def add_desis( array_args (dict, optional): Additional arguments to pass to `array_to_memory_file` when reading the raster. Defaults to {}. """ + if array_args is None: + array_args = {} if isinstance(source, str): @@ -515,7 +591,7 @@ def add_neon( layer_name="NEON", zoom_to_layer=True, visible=True, - array_args={}, + array_args=None, method="nearest", **kwargs, ): @@ -557,6 +633,8 @@ def add_neon( Defaults to "nearest". """ + if array_args is None: + array_args = {} xds = None if isinstance(source, str): @@ -598,7 +676,7 @@ def add_aviris( layer_name="AVIRIS", zoom_to_layer=True, visible=True, - array_args={}, + array_args=None, method="nearest", **kwargs, ): @@ -639,6 +717,8 @@ def add_aviris( method (str, optional): The method to use for data interpolation. Defaults to "nearest". """ + if array_args is None: + array_args = {} xds = None if isinstance(source, str): @@ -668,19 +748,19 @@ def add_aviris( self.cog_layer_dict[layer_name]["hyper"] = "AVIRIS" self._update_band_names(layer_name, wavelengths) - def add_hyper(self, xds, type, wvl_indexes=None, **kwargs): + def add_hyper(self, xds, dtype, wvl_indexes=None, **kwargs): """Add a hyperspectral dataset to the map. Args: xds (str): The Xarray dataset containing the hyperspectral data. - type (str): The type of the hyperspectral dataset. Can be one of + dtype (str): The type of the hyperspectral dataset. Can be one of "EMIT", "PACE", "DESIS", "NEON", "AVIRIS". **kwargs: Additional keyword arguments to pass to the corresponding add function. """ if wvl_indexes is not None: - if type == "XARRAY": + if dtype == "XARRAY": kwargs["indexes"] = [i + 1 for i in wvl_indexes] else: @@ -690,17 +770,17 @@ def add_hyper(self, xds, type, wvl_indexes=None, **kwargs): .values.tolist() ) - if type == "EMIT": + if dtype == "EMIT": self.add_emit(xds, **kwargs) - elif type == "PACE": + elif dtype == "PACE": self.add_pace(xds, **kwargs) - elif type == "DESIS": + elif dtype == "DESIS": self.add_desis(xds, **kwargs) - elif type == "NEON": + elif dtype == "NEON": self.add_neon(xds, **kwargs) - elif type == "AVIRIS": + elif dtype == "AVIRIS": self.add_aviris(xds, **kwargs) - elif type == "XARRAY": + elif dtype == "XARRAY": kwargs.pop("wavelengths", None) self.add_dataset(xds, **kwargs) diff --git a/hypercoast/ui.py b/hypercoast/ui.py index a67a03a7..73c3b672 100644 --- a/hypercoast/ui.py +++ b/hypercoast/ui.py @@ -12,7 +12,6 @@ import numpy as np import xarray as xr from bqplot import pyplot as plt -from IPython.core.display import display from ipyfilechooser import FileChooser from .pace import extract_pace from .desis import extract_desis @@ -272,7 +271,7 @@ def handle_interaction(**kwargs): self._fig.axes = self._fig.axes[1:] elif isinstance(self._fig.axes[-1], bqplot.ColorAxis): self._fig.axes = self._fig.axes[:-1] - except: + except Exception: pass plt.xlabel(xlabel)